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.
Connect with Us