SAFEARRAYs Made Easier

By: Andrew Ames

Abstract: This document describes the VCL template classes found in safearry.h with C++ Builder 5 and demonstrates their use to make working with SafeArrays much easier.

SAFEARRAYs Made Easier

Working with the Win32 SAFEARRAY data type can be rather tedious. This technical information document describes the VCL template classes found in safearry.h with C++ Builder 5 and demonstrates their use to make working with SafeArrays much easier. Each code snippet will compile as is in a straight Win32 application.

What Is a SAFEARRAY?
typedef struct tagSAFEARRAYBOUND
\{
    ULONG cElements;
    LONG lLbound;
\} SAFEARRAYBOUND;

typedef struct tagSAFEARRAY
\{
    USHORT cDims;
    USHORT fFeatures;
    ULONG cbElements;
    ULONG cLocks;
    PVOID pvData;
    SAFEARRAYBOUND rgsabound[1];
\} SAFEARRAY;

The SAFEARRAY data type was designed to be used in Automation to pass multidimensional arrays to and from COM interface methods. This type is a structure that contains boundary information as well as a reference to the data. This allows the passing of arrays between COM Apartments and/or processes without the need to implement a marshaler for custom COM interfaces. The SAFEARRAY type, being automation-compatible itself, can only consist of automation-compatible data. Following is the straight Win32 API code required to construct, use, and destroy a SAFEARRAY.

#include <vcl.h>
#include <windows.h>
#pragma hdrstop

//---------------------------------------------------------------------------

void Fill2DSafeArray(LPSAFEARRAY lpsaArray, long Value);

#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
\{
   // Create 2D array of long integers. Similar to...
   // long Array[20][10];
   SAFEARRAYBOUND sabdBounds[2] = \{ \{10, 0\}, \{20, 0\} \};
   LPSAFEARRAY lpsaArray = SafeArrayCreate(VT_I4, 2, sabdBounds);

   // Fill array with values
   Fill2DSafeArray(lpsaArray, 5);

   // Destroy array
   SafeArrayDestroy(lpsaArray);
   return 0;
\}
//---------------------------------------------------------------------------
void Fill2DSafeArray(LPSAFEARRAY lpsaArray, long Value)
\{
   int lower0 = lpsaArray->rgsabound[0].lLbound;
   int upper0 = lpsaArray->rgsabound[0].cElements + lower0;
   long* pData = (long*)lpsaArray->pvData;
   for(int i = lower0; i < upper0; i++) \{
      int lower1 = lpsaArray->rgsabound[1].lLbound;
      int upper1 = lpsaArray->rgsabound[1].cElements + lower1;
      for(int j = lower1; j < upper1; j++) \{
         *pData = Value;
         pData++;
      \}
   \}
\}
//---------------------------------------------------------------------------

If you've never used the SAFEARRAY type, you can see the difficulties it can bring. First of all, us C++ programmers lose the benefit of strong compile-time type-checking. Also, instead of simply using the index-of operator, we must access the memory where the data is stored directly, which is very error prone. The equivalent code in ANSI-compliant C++ for a 2D array of long integers reducing to the following.

#include <vcl.h>
#include <windows.h>
#pragma hdrstop

//---------------------------------------------------------------------------

void Fill2DArray(long* pArray, int size1, int size2, long Value);

#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
\{
   // Create 2D array of long integers. Similar to...
   long Array[20][10];

   // Fill array with values
   Fill2DArray(lpsaArray, 20, 10, 5);

   return 0;
\}
//---------------------------------------------------------------------------
void Fill2DArray(long* pArray, int size1, int size2, long Value)
\{
   for(int i = 0; i < 20; i++) \{
      for(int j = 0; j < 10; j++) \{
         *pData = Value;
         pData++;
      \}
\}
//---------------------------------------------------------------------------

Although the C++-only version is much less error prone, it does not contain boundary information and so must be explicitly passed as arguments to Fill2DArray. Also, it cannot be used for automation because it cannot be used to pass interface pointers between apartments and/or processes.

Their are several VCL class templates that aid in creating, manipulating, and managing SafeArrays with the benefit of strong compile-time type-checking that can be passed to or retrieved from COM interface methods that support COM's standard marshaler. The header file, safearry.h, contains the source for template class TSafeArray and related classes with examples of their use. Refer to this header file to supplement this document.

TArrayDimT and TSafeArrayT
TArrayDimT and TSafeArrayT are template wrapper classes for SAFEARRAYBOUND and SAFEARRAY, respectively. Following is the declaration of these two class templates.

// SAFEARRAYBOUND[] wrapper
//
template <int DIM>
class TArrayDimT
\{
public:
  TArrayDimT<DIM>(int dim1Len);
  TArrayDimT<DIM>(int dim1Len, int dim2Len);
  TArrayDimT<DIM>(int dim1Len, int dim2Len, int dim3Len);

  operator SAFEARRAYBOUND* ();

  void  set_BoundsLength(int index, long length);
  long  get_BoundsLength(int index);

  __property  long BoundsLength[int index] = \{ read = get_BoundsLength, write = set_BoundsLength \};

protected:
  SAFEARRAYBOUND m_Bounds[DIM];
\};

// SAFEARRAY* wrapper
//
template <class T, VARENUM vt, int DIM>
class TSafeArrayT
\{
public:
  TSafeArrayT();
  TSafeArrayT(SAFEARRAY *psa);
  TSafeArrayT(TArrayDimT<DIM>& dim);
 ~TSafeArrayT();

  TSafeArrayT(const TSafeArrayT<T, vt, DIM>& src);
  TSafeArrayT<T, vt, DIM>& operator=(const TSafeArrayT<T, vt, DIM>& src);

  TSAAccessorT<T, DIM> operator[] (int index);
  SAFEARRAY**        operator& ();

  long        get_BoundsLength(long idx) const;
  VARENUM     get_VarType() const;
  unsigned    get_Dimension() const;

  long        Elements();

  void        Attach(SAFEARRAY* psa);
  SAFEARRAY*  Detach();
  void        Destroy();

  __property long BoundsLength[long idx] = \{ read=get_BoundsLength \};

  __property VARENUM  VarType   = \{ read=get_VarType \};
  __property unsigned Dimension = \{ read=get_Dimension \};

protected:
  SAFEARRAY   *m_psa;

private:
\};

There are many ways to construct, manipulate, and destroy SafeArrays using these classes. In addition, every type of multidimensional array is not inherantly supported by these classes as is, so you may need to extend them a bit. The following code constructs the same SAFEARRAY from the preceding examples using three different means.

//---------------------------------------------------------------------------

#include <vcl.h>
#include <windows.h>
#include <safearry.h>
#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
\{
   // Method 1
   TArrayDimT<2> Dim_1(20, 10);
   TSafeArrayT<long, VT_I4, 2> Array2D_1(Dim_1);

   // Method 2
   TSafeArrayDim2 Dim_2(20, 10);
   TSafeArrayLong2 Array2D_2(Dim_2);

   // Method 3
   SAFEARRAYBOUND sabdBounds[2] = \{ \{10, 0\}, \{20, 0\} \};
   LPSAFEARRAY lpsaArray = SafeArrayCreate(VT_I4, 2, sabdBounds);
   TSafeArrayLong2 Array2D_3(lpsaArray);

   return 0;
\}
//---------------------------------------------------------------------------

The first method provides all the template arguments for TArrayDimT and TSafeArrayT. The second method uses typedefs for commonly used arrays. See the bottom of safearry.h for these typedefs among others. The third method first constructs a SAFEARRAY using Win32  and then attaches the SAFEARRAY to a TSafeArrayLong2. Each of the three SafeArrays are destroyed in the destructor of TSafeArrayT when they go out of scope, which leads us to the next topic.

Accessing TSafeArrayT Elements
Modifying the elements of the array is very simple. The index of operator ([]) has been overloaded for this purpose. This operator returns a reference to a TSAAccessorT object that can also has the index of operator overloaded so that multidimensional arrays can be accessed by a series of index of operations. However, there is a small but significant bug in the destructor of TSAAccessorT that makes it unusable for multidimensional arrays. In safearry.h starting on line 220 is the following:

~TSAAccessorT()
 \{
   if (m_Alloc) \{
     delete[] m_Indices;
   \}
 \}

This should read:

~TSAAccessorT()
 \{
   if (m_Alloc) \{
     m_Indices--;
     delete[] m_Indices;
   \}
 \}

Now, the following program will not give an access violation.

//---------------------------------------------------------------------------

#include <vcl.h>
#include <windows.h>
#include <safearry.h>
#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
\{
   TSafeArrayDim3 dim(100, 10, 20);
   TSafeArrayLong3 array(dim);
   for(int i = 0; i < 100; i++)
      for(int j = 0; j < 10; j++)
         for(int k = 0; k < 20; k++)
            array[i][j][k] = 5;

   return 0;
\}
//---------------------------------------------------------------------------

Automatic Destruction of SafeArrays
Another benefit to using a statically instantiated object to manage SafeArrays is for automatic destruction of the internal SAFEARRAY when the object goes out of scope. For example, the following code instantiates a SafeArray in a try...catch and a SafeArray in a function called within the try...catch block. Both SafeArrays are automatically destroyed when they go out of scope, whether or not an exception occurs. This example also demonstrates passing TSafeArrayT objects by reference and by value and manipulating the data contained in the array.

//---------------------------------------------------------------------------

#include <vcl.h>
#include <windows.h>
#include <safearry.h>
#pragma hdrstop

//---------------------------------------------------------------------------
TSafeArrayBSTR1 CreateBSTRArray(TSafeArrayT<wchar_t, VT_I2, 2>& SafeArray) \{
   TSafeArrayDim1 Dim(SafeArray.BoundsLength[0]);
   TSafeArrayBSTR1 BSTRArray(Dim);

   WideString wstr;
   for(int i = 0; i < SafeArray.BoundsLength[0]; i++) \{
      wstr.Empty();
      for(int j = 0; j < SafeArray.BoundsLength[1]; j++) \{
         wstr.Insert(WideString(SafeArray[j][i]), wstr.Length()+1);
      \}
      BSTRArray[i] = wstr.Detach();
   \}

   return BSTRArray;
\}

#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
\{
   try \{
      // Fill a 10x10 array of chars with where each row consists of
      // chars '0', '1', '2',...,'9'
      TSafeArrayDim2 Dim(10, 10);
      TSafeArrayT<wchar_t, VT_I2, 2> Array(Dim);
      char temp[2];
      for(int i = 0; i < 10; i++)
         for(int j = 0; j < 10; j++)
            Array[j][i] = WideChar((itoa(j, temp, 10))[0]);

      // Create an array of 10 BSTRS, where each BSTR consists
      // of the string, "0123456789". Each row in the 10x10 array
      // of chars is converted to the equivalent BSTR.
      TSafeArrayBSTR1 BSTRArray = CreateBSTRArray(Array);

      // Display the BSTRs.
      WideString msg;
      for(int i = 0; i < 10; i++)
         msg = msg + WideString(BSTRArray[i]) + WideString("n");
      ShowMessage(msg);
   \} catch(Exception& E) \{
      ShowMessage(E.Message);
   \}

return 0;
\}
//---------------------------------------------------------------------------

This program demonstrates the numerous ways TSafeArrayT can be used to create, modify, and convert data, while letting the exception handling mechanism automatically clean up resources if an error occurs.

SAFEARRAY Ownership
Care must be taken when accessing the SAFEARRAY structure stored by a TSafeArrayT directly in order to avoid the destruction of the SAFEARRAY in more than one place or not at all. Often, when using straight Win32 functions or those of another API that work directly with the SAFEARRAY structure, you still want to use TSafeArrayT to manage the data. The following code demonstrates three cases: one, a newly created SAFEARRAY is returned from a function; two, a newly created SAFEARRAY is returned in an out parameter; three, where a preexisting SAFEARRAY is to be passed to a function.

#include <vcl.h>
#include <windows.h>
#include <safearry.h>
#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
\{
   SAFEARRAYBOUND sabdBounds[2] = \{ \{10, 0\}, \{20, 0\} \};
   TSafeArrayLong2 Array1(SafeArrayCreate(VT_I4, 2, sabdBounds));

   TSafeArrayLong2 Array2;
   SAFEARRAY* sa = Array1.Detach();
   SafeArrayCopy(sa, &Array2);
   SafeArrayDestroy(sa);

   return 0;
\}
//---------------------------------------------------------------------------

The first two lines of WinMain initiliaze Array1 with the return value of SafeArrayCreate. At this point, ownership of the SAFEARRAY is in the hands of Array1. The only method provided to access the internal SAFEARRAY is by detaching it with TSafeArrayT::Detach(). Once this method is called, the SAFEARRAY is no longer associated with Array1 and must be destroyed explicitly, as demonstrated the example. Not doing so will cause a resource leak. Array2 is constructed with no data or dimension sizes. Its internal SAFEARRAY is accessed and initialized by the Win32 SafeArrayCopy function. The & operator was overloaded for initializing TSafeArrayT objects by using them as an out parameter. This will only work if the TSafeArrayT object has not yet been initialized. If it has already, an exception is thrown. SAFEARRAY objects can be detached and reattached to TSafeArrayT objects as many times as desired. However, attempting to attach a SAFEARRAY that does not match the type and dimensions of the TSafeArrayT object will result in an exception.

#include <vcl.h>
#include <windows.h>
#include <safearry.h>
#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
\{
   SAFEARRAYBOUND sabdBounds[2] = \{ \{10, 0\}, \{20, 0\} \};
   TSafeArrayLong2 Array1(SafeArrayCreate(VT_I4, 2, sabdBounds));

   TSafeArrayLong2 Array2;
   SAFEARRAY* sa = Array1.Detach();
   SafeArrayCopy(sa, &Array2);
   Array1.Attach(sa);

   return 0;
\}
//---------------------------------------------------------------------------

This last example is identical to the one before, except that, instead of explicitly destroying the SAFEARRAY sa, it is reattached to Array1 which destroys it when Array1 goes out of scope.



Server Response from: ETNASC04