Passing DYNAMIC Data QUICKLY using SafeArrays - 2

This builds upon the information given by Aravid Corera in his article "Using a Multidimensional SAFEARRAY to pass data across from COM objects." It replaces Richard Warg's article "Using a BSTR to Transport C++ Objects Across the DCOM Interface." Although a creative idea, it is not a good idea to use BSTR for things other than text.

This code shows how to pass data of any byte specific size, which may or may not be known until runtime, through a SafeArray. This often occurs with data such as bitmaps, binary files, or sound data. These are considered dynamic data because their size and content can change from one session to another. Furthermore, it shows how to do it as quick as possible. It also shows how to pack data from different variables into the single array. The examples given are designed to be used in C++. Other languages may or may not be able to determine the information packed into the array.

The first example uses memcpy() to copy the data directly into the SafeArray data. This way is safe across all (D)COM processes, because it uses the standard COM marshaller once the data is passed to the caller. The reason for using memcpy() is speed. Calling SafeArrayPutElement() and SafeArrayGetElement() can get really slow. Imagine calling them 2048 times( when getting and retrieving the data ) for a 1kb file. COM is slow enough, no more overhead is needed.

The second example just passes a pointer to an area of memory that contains the data needed. This is ONLY safe when using INPROC DLLs. I included this as more of an example on how to past data as fast as possible, not necessarily the best and safest way.

Example 1:

The SafeArray is created based on the total size of the data. Next we get access to the data directly. Finally, using memcpy() the data is just transferred right into the array.
Unpacking the data is pretty much exactly the reverse. The demo project and source code shows how to do it. Notice that the header is passed in first, then using pointer arithmetic, the bitmap bits are passed in.


HRESULT __stdcall CCOMDynamicData::PassBitmapCopy(LPSAFEARRAY* ppsaDynamic)
{
	long lBmpSize;
	if ( !m_bmp.bmBits )
	{
		lBmpSize = LoadDDBitmap();
		if ( lBmpSize == 0 )
		return S_FALSE;
	}
	else
		lBmpSize = m_bmp.bmWidthBytes * m_bmp.bmHeight;

	// Create the storage
	// notice the size is that of the header storage and of the bits
	SAFEARRAY* psaBitmapBits = ::SafeArrayCreateVector( VT_UI1, 0, sizeof(BITMAP ) + lBmpSize );
	LPVOID lpArrayData;
	::SafeArrayAccessData( psaBitmapBits, &lpArrayData );
	// copy the header
	::memcpy( lpArrayData, &m_bmp, sizeof( BITMAP ) );
	// copy the data
	// offset by the bitmap
	::memcpy( (char *)lpArrayData + sizeof( BITMAP ), m_bmp.bmBits,BmpSize );
	::SafeArrayUnaccessData( psaBitmapBits );

	*ppsaDynamic = psaBitmapBits;

	return S_OK;
}

Example 2:

Just pass a pointer to where the data is at. This is fast, but ONLY works in INPROC servers. If you have no need for compatibility and will only be using INPROC servers, then this is the way for you.


HRESULT __stdcall CCOMDynamicData::PassBitmapPtr(BITMAP* pBmp)
{
	if ( m_bmp.bmBits ) // if we already got the bitmap
	{
		*pBmp = m_bmp; // just return it
		return S_OK;
	}
	// else we gotta get bitmap information

	if ( LoadDDBitmap() == 0 )
		return S_FALSE;

	*pBmp = m_bmp;

	return S_OK;
}

The code in the zipped files below passes a bitmap structure between an INPROC COM server and a client. Once the bitmap is retrived it is displayed on the screen. Change the Bitmap in the COM DLL to demonstrate to yourself how the size of a bitmap can be determined at runtime and then still have it's data passed successfully.

Download demo project - 66 KB

Download source - 5 KB