March 2017

Volume 32 Number 3

[C++]

Introducing the SAFEARRAY Data Structure

By Giovanni Dicanio | March 2017

This is a companion article to Simplify Safe Array Programming in C++ with CComSafeArray

Introducing the SAFEARRAY Data Structure

A safe array is represented using a C data structure named SAFEARRAY, which is defined in the <OAIdl.h> Windows Platform SDK header. The SAFEARRAY structure is described in detail in the Windows Dev Center (bit.ly/2fLXY6K):

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

Basically, the SAFEARRAY data structure is a safe array descriptor. It contains various pieces of information describing a particular instance of a safe array, like its number of dimensions (or rank, stored in the cDims field), each dimension’s bounds (stored in the rgsabound field), a lock count (cLocks), and a pointer to the actual safe array’s data (pvData). A safe array is usually handled in code via a pointer to its SAFEARRAY descriptor, that is, SAFEARRAY*.

A common case is having single-dimension safe arrays, also known as arrays of rank one. On the other hand, a 2D matrix would be stored in a two-dimensional safe array, and its rank would be two. You can think of the rank as the number of indexes required to access an item in the array. To access items in a single-dimensional vector just one index is required, so its rank is one. On the other hand, to access items in a 2D matrix, two indexes (a row and column index) are required, so the rank is two. (Note: For better efficiency, it’s also possible to flatten a 2D matrix into a one-dimensional safe array, for example, storing the matrix elements row-wise.)

The Windows Platform SDK offers C API functions to manipulate safe arrays, for example to create them, to access their data for both reading and writing, to destroy them, and so on.

Creating a Safe Array

Suppose you want to create an array storing 10 doubles. This is an array of rank one because it’s just a single-dimensional array. One index is sufficient to access the elements in the array. For each safe array’s dimensions, you have to define the associated bounds using the SAFEARRAYBOUND data structure.

Because this safe array has just one dimension, you need just one instance of SAFEARRAYBOUND. For each dimension, you can specify a lower bound that can potentially be different than zero, and the total number of elements for that dimension. It’s common to set the lower bound to zero, as this reflects the convention used by several programming languages, including C, C++ and C#, to start indexing array elements from zero. This can be expressed in C++ code as follows:

SAFEARRAYBOUND saBound;
saBound.lLbound = 0;
saBound.cElements = 10;

The lLbound field stores the lower-bound of the safe array, that is, the index of the array’s first element: in this case, zero. Instead, cElements represents the count of elements in the array, in this case 10.

Then, the SafeArrayCreate function can be called, to create the desired safe array. This function has the following prototype:

SAFEARRAY* SafeArrayCreate(
  _In_ VARTYPE        vt,
  _In_ UINT           cDims,
  _In_ SAFEARRAYBOUND *rgsabound
);

Basically, SafeArrayCreate takes three input parameters. The first parameter, vt, represents the type of the elements stored in the array. An extensive list of available types can be found in the <wtypes.h> Windows SDK header, immediately following the VARTYPE typedef (at least in the headers shipped with VS2015). For example, for a safe array containing 4 byte signed integers, the correct value for the vt parameter is VT_I4; the “I” stands for integer, and 4 is the number of bytes for a single integer. On the other hand, for a safe array storing a sequence of bytes, which can be represented as unsigned chars, the correct value is VT_UI1. Moreover, a safe array of doubles will use VT_R8. The “R” stands for “real” (as doubles are an approximation of real numbers), and 8 represents the number of bytes in a double. For a safe array storing strings that are represented using the BSTR type, the correct value for vt is VT_BSTR. And so on.

A safe array storing doubles can be created invoking SafeArrayCreate like this:

SAFEARRAY* psa = SafeArrayCreate(VT_R8, 1, &saBound);

VT_R8 tells SafeArrayCreate to create a safe array storing doubles. The following parameter is the number of dimensions, or rank, of the safe array: in this example, it’s just one, for a single-dimensional safe array. And finally, you pass a pointer to SAFEARRAYBOUND descriptors; in this case there’s just one descriptor, for the single array dimension.

Similarly, a safe array of bytes can be created specifying VT_UI1 instead of VT_R8:

SAFEARRAY* psa = SafeArrayCreate(VT_U1, 1, &saBound);

If SafeArrayCreate succeeds, it returns a pointer to a SAFEARRAY descriptor that can be used to manipulate the safe array. On failure, this C-interface function returns NULL (in C++, you can use nullptr instead).

Destroying a Safe Array

If you write code that owns the SAFEARRAY, once you’re done with it, you must call the SafeArrayDestroy function to release the memory allocated by the safe array. That function accepts a single parameter, that is, a pointer to a SAFEARRAY descriptor associated to the safe array to be deleted. The syntax to destroy a safe array is very simple:

// SAFEARRAY* psa
SafeArrayDestroy(psa);

// Avoid dangling pointers
*psa = nullptr;

Note that when you transfer the safe array ownership to other components, you must not call SafeArrayDestroy in your code, to avoid double-destruction bugs: The new owner will be responsible for destroying the safe array handed to it.

Accessing Safe Array’s Data

Once you have a valid SAFEARRAY descriptor, you can call the SafeArrayLock function to access the safe array’s data. This is the prototype of the function:

HRESULT SafeArrayLock(
  _In_ SAFEARRAY* psa
);

Just like for SafeArrayDestroy, the only parameter is a pointer to a safe array descriptor. On success, SafeArrayLock increments a lock count associated to the input safe array and places a pointer to the array data in the pvData field of the safe array descriptor. As its “pv” prefix suggests, the pvData field is a “void*” pointer. That’s because the safe array is a polymorphic C array structure and the data stored in it can be of many different types, from integers of different sizes, to floating point numbers, to BSTR strings, to COM IUnknown interface pointers, and so on. Once you call SafeArrayLock, it’s a good habit to check the returned HRESULT and only in case of success you can access the SAFEARRAY::pvData field. Note that since pvData is a void*, you have to static_cast it to the actual type of data stored in the safe array. For example, assuming a safe array of BYTEs, the code to access the safe array’s data can look like this:

// SAFEARRAY* psa is a pointer to a valid safe array descriptor
HRESULT hr; // result of operations
hr = SafeArrayLock(psa);
if (SUCCEEDED(hr)) 
{
  // Access safe array’s data via psa->pvData
  BYTE* pData = static_cast<BYTE*>(psa->pvData);
   
  // Process data ...
}

Once you’ve completed the access to the safe array’s data, you must call the SafeArrayUnlock function, which is the symmetric of SafeArrayLock, again passing a pointer to the safe array descriptor:

// Release the previous lock on the safe array
SafeArrayUnlock(psa);

This function will decrement the lock count on the safe array, and when there are no more locks on that safe array object, it’ll be possible to resize or free it.

In addition to the SafeArrayLock/SafeArrayUnlock pair, there are also the SafeArrayAccessData and SafeArrayUnaccessData matching functions. When you call SafeArrayAccessData, the pointer to the safe array’s data is specified as an additional output parameter:

// Pointer to safe array’s data, returned as output parameter
BYTE* pData = nullptr;
hr = SafeArrayAccessData(psa, 
                         reinterpret_cast<void**>(&pData));
if (FAILED(hr)) 
{
  // Handle error ...
}

Note the required reinterpret_cast, as SafeArrayAccessData expects a void** output pointer parameter. There’s a double pointer indirection here because void* is a pointer to a generic polymorphic C array, and because this is an output parameter, another level of pointer indirection is required. Again, when you’re done with the safe array’s data, you must call SafeArrayUnaccessData to release access to the safe array. Note that the safe array is still live in memory, until SafeArrayDestroy is called on it.

This is a companion article to Simplify Safe Array Programming in C++ with CComSafeArray


Giovanni Dicanio is a computer programmer specializing in C++ and the Windows OS, a Pluralsight author (bit.ly/GioDPS) and a Visual C++ MVP. Besides programming and course authoring, he enjoys helping others on forums and communities devoted to C++. He can be contacted at giovanni.dicanio@gmail.com. He also blogs on msmvps.com/gdicanio.

Thanks to the following technical experts for reviewing this article: David Cravey and Marc Gregoire
David Cravey is an Enterprise Architect at GlobalSCAPE, leads several C++ user groups, and was a four-time Visual C++ MVP.
Marc Gregoire is a senior software engineer from Belgium, the founder of the Belgian C++ Users Group, author of “Professional C++” (Wiley), co-author of “C++ Standard Library Quick Reference” (Apress), technical editor on numerous books, and since 2007, has received the yearly MVP award for his VC++ expertise. Marc can be contacted at marc.gregoire@nuonsoft.com.