Using COM to Pass Arrays

OVERVIEW:

COM is a great tool when programming in windows. It gives us the ability to communicate with other programs and allows us to scale our products.  There are a few things we are are used to doing however, that are more difficult with COM, one of these is passing arrays as parameters in COM methods.  This article describes the process to pass arrays of data to and from COM objects, without writing our own marshaler.

BACKGROUND:

It's a myth really that COM only supports VARIANT data type as parameters.  Most COM developers don't know about this because we are used to using the wizards that only provide these as options.  We can however, pass any datatypes we want, even structs and arrays, but if we need this functionality then we must write our own marshaler.   This is not really a hard task, but it does require some additional time, and for most applications, it's much easier to use the system marshaler (oleaut32.dll).  The cost of using the system marshaler is we must use VARIANT types.

To pass an array of data, we have two choices.  We can write our own marhaler, or we can send the array as a VARIANT.  The VARIANT structure (which is what it really is) allows for a an element called SAFEARRAY.  The examples below show how to do this.

EXAMPLES:

This is an example of how to send a file via COM to a server, who in turn writes the file to it's own disk system.  This is kind of like a file transfer protocol via COM.   It's not the quickest way to do it, but it works, and more importantly, we used COM, and we did it without writing our own marshaler.

//The Client


BOOL CClient::StreamIn()
{
	char *fBuf;
	fBuf = (char*)malloc(4096); // Allocate our buffer

	VARIANT varTemp;
	varTemp.vt = VT_UI1 | VT_ARRAY;
	SAFEARRAYBOUND bound;
	long lLen = m_File->GetLength();

	long nBytesRead = m_File->Read( fBuf, 4096 ); // Read from our file into our buffer (note: file opened as binary)
	int iCount = 4096;
	
	while(nBytesRead>0) // While we read bytes
	{
	
		bound.cElements = nBytesRead; // Set up size of array
		bound.lLbound = 0;
		varTemp.parray = SafeArrayCreate(VT_UI1, 1, &bound); // Create it
		void* pDest;
		SafeArrayAccessData(varTemp.parray, &pDest);
		memcpy(pDest, fBuf, nBytesRead); // Copy into array
		SafeArrayUnaccessData(varTemp.parray);

		long retval = 0;
		// m_server was created using the smart pointers (#import "yourserver.tlb" no_namespace ... etc)
	
		
		HRESULT _hr = m_server->raw_SendFile(varTemp); // Call the server method, pass our VARIANT
		if(retval != 0 && !FAILED(_hr))
		{
			free(fBuf);
			return false;
		}
	
		SafeArrayDestroy(varTemp.parray); // destroy the array.

		nBytesRead = m_File->Read( fBuf, 4096 );  Read moredata

	} // End of while
	free(fBuf);
	return true;

}
//The Server
// IDL:
	[id(1), helpstring("method SendFile")] HRESULT SendFile([in] VARIANT buffer);
// Implementation
STDMETHODIMP CServer::SendFile(VARIANT buffer)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState()) // To support MFC

	void* pDest;
	
	// Previously the server object had opened it's own file
	if(m_File!=NULL) // Which should mean that it is open
	{
		if (buffer.vt == (VT_ARRAY | VT_UI1))
		{
			SafeArrayAccessData(buffer.parray, &pDest); // Get the data
			SafeArrayUnaccessData(buffer.parray);	    // Unaccess it.
			m_File->Write(pDest, buffer.parray->rgsabound->cElements); // Write it to the server file
		}


	}


	return S_OK;
}

NOTES:

We could have used SAFEARRAY's and VARIANT by using MFC or ATL wrapper classes, but the examples above are easier to understand. Also, we could have added better try catch blocks etc, but I've taken them out to simply the example.  I'll leave that up to you.   In process this code is very fast, out of process or out of band (DCOM), you'll want to set the buffer size at least as high as your network packet size, if not more.   What makes this slow is calling the method over and over.  Alternatively, we could have passed a com interface to the function, but DCOM implementations make this a nightmare.



Comments

  • how to pass an array of struct from VB script to COM

    Posted by Legacy on 07/03/2003 12:00am

    Originally posted by: yuexiang

    who can help me
    thanks!

    Reply
  • Problem in passing variant

    Posted by Legacy on 04/26/2003 12:00am

    Originally posted by: shailesh


    Hey i am passing the variant variable to a function by preparing the data as follows. Can anybody tell me where is the problem in this code. I am not getting any error but it is not showing any data. Thanks


    VariantInit(&MyVar);
    MyVar.vt = VT_VARIANT | VT_ARRAY;

    SAFEARRAY *pSA;
    SAFEARRAYBOUND aDim[1];
    // a one dimensional array
    aDim[0].lLbound = 0;
    aDim[0].cElements = 1938;
    pSA = SafeArrayCreateVector (VT_VARIANT,0,1938);
    void *pDest;
    SafeArrayAccessData(pSA, &pDest);
    memcpy(pDest, tblob.lpb, 1937); // Copy into array
    SafeArrayUnaccessData(MyVar.parray);

    MyVar.parray= pSA;
    m_InkCtrl.SetImageData(MyVar);
    SafeArrayDestroy(pSA); // destroy the array.

    Reply
  • Can't pass ARRAY in this way!

    Posted by Legacy on 10/26/2001 12:00am

    Originally posted by: Xu Fayan

    I am going to use VB activeX dll in my VC++ project,
    
    but I can't call VB's dll function like below, for the parameter is an array.


    VB DLL
    ***************************************

    Public Sub b(a() As Integer, d As Integer)

    Dim sum As Integer
    Dim i As Integer
    sum = 0
    For i = 0 To d
    sum = sum + CInt(a(i))
    Next i
    MsgBox CStr(sum)

    End Sub
    ****************************************
    ..............


    VC Client
    ***************************************

    VARIANT varTemp;
    varTemp.vt = VT_I4 | VT_ARRAY;
    SAFEARRAYBOUND bound;


    bound.cElements = 4; // Set up size of array
    bound.lLbound = 0;
    varTemp.parray = SafeArrayCreate(VT_I4, 1, &bound); // Create it
    void* pDest;

    SafeArrayAccessData(varTemp.parray, &pDest);

    memcpy(pDest, c, sizeof(c)); // Copy into array

    SafeArrayUnaccessData(varTemp.parray);
    // t is the object of VB DLL class
    t->b(&varTemp.parray, (short *)&d);


    *******************************************


    Can any body can help me to solve it?

    Thanks advancedly.

    Reply
  • Dont d this if you are saving to an IPropertyBag!

    Posted by Legacy on 09/20/2001 12:00am

    Originally posted by: Ryan Mills

    I have a void* buffer of n bytes, which I can happily pass around functions using a SAFEARRAY. If I use it to persist data in my control (i.e., I have a control embedded in a web page), my control implements IPersistPropertyBag_Load and has to call:

    pPropBag->Write(bstrPropertyName, &vVar));

    ...where vVar is a CComVariant object containing my SAFEARRAY. However, because the VARTYPE is VT_UI1|VT_ARRAY, the implementation of IPropertyBag sees a string of chars and therefore only stores data up to the first null character!!!!!!

    Reply
  • Thank You!!

    Posted by Legacy on 08/01/2001 12:00am

    Originally posted by: Mike Pulice

    I am new to COM but have a big project going, typical.
    I was stuck on the large packets I have to handle so I figured go to CodeGuru. I was relieved when I saw your article. Thanks for posting it!

    Also, thanks for the IStream hints too.

    Mike

    Reply
  • What us quicker

    Posted by Legacy on 02/02/2001 12:00am

    Originally posted by: Volodymyr Kozatchenko

    What design is quicker (and tackes less memory) for getting/putting in COM?

    Variant 1) ATL using uviversal marsh. (i.e. oleautomation in .IDL
    [id(1), helpstring("method GetData")] HRESULT PutDataV([in] VARIANT pVar);
    [id(1), helpstring("method GetData")] HRESULT GetDataV([out] VARIANT pVar);

    Variant 2) ATL using its proxy/stub marsh. dll (made with nmake...)
    [id(2), helpstring("method PutFoo")]
    HRESULT PutFoo([in] short m, [in, size_is(m)] char * pbyte);
    [id(3), helpstring("method GetFoo")] HRESULT GetFoo([in] short ms, [out, size_is(ms)] unsigned char * pbyte);


    Thank you.

    Reply
  • Alloc SafeArray too long

    Posted by Legacy on 08/02/2000 12:00am

    Originally posted by: Riccardo Raccuglia

    if I reading 4096 byte from files I can alloc a safe array with cElements = 4096 / sizeof(UINT) ?

    Reply
  • pass an array of user-defined structures from a vb client to c++ server?

    Posted by Legacy on 05/18/2000 12:00am

    Originally posted by: Unsin

    How do we pass an array of user-defined structures via COM from a vb client to c++ server? 
    
    

    Thanks

    Reply
  • Passing any kind of data

    Posted by Legacy on 01/27/2000 12:00am

    Originally posted by: DoubleJ

    In case you have both client and server implemented in C++
    
    the easiest way to pass any kind of data is to use IStream interface.
    This interface can be marshalled across process/machine boundaries
    so it is universal and safe method.

    Here is fragment of server side code (no error checking):

    STDMETHODIMP CServer::GetData( LPSTREAM *ppStm )
    {
    ...
    // Create stream in memory
    // For better performance and to avoid reallocation
    // you should preallocate memory using
    // HGLOBAL hGlob = GlobalAlloc( GMEM_MOVEABLE, dwLen )
    // and use hGlob instead of NULL in following function
    HRESULT hr = CreateStreamOnHGlobal( NULL, TRUE, ppStm );
    // Write some C++ data
    hr = *ppStm->Write( &SomeValue, sizeof( SomeValue ), NULL );
    // Write some COM objects that implement IPersistStream interface
    OleSaveToStream( com_cast<IPersistStream>(pSomeObject), *ppStm );
    ...
    // Data written
    // Seek to start of stream (for client comfort)
    ULARGE_INTEGER liNewPosition;
    LARGE_INTEGER liOff;
    LISet32(liOff, 0);
    hr = *ppStm->Seek(liOff, STREAM_SEEK_SET, &liNewPosition);

    return hr;
    }

    Here is fragment of client side code (no error checking):

    ...
    CComPtr<IStream> pStm = NULL;
    pServer->GetData( &pStm );
    // Read C++ data from stream
    pStm->Read( &SomeValue, sizeof( SomeValue ), NULL );
    // Read COM objects
    CComPtr<ISomeObject> pSomeObject = NULL;
    OleLoadFromStream( pStm, __uuidof(ISomeObject), (LPVOID*)&pSomeObject );
    ...

    Hope this helps.


    Reply
  • How to pass array of COM objects?

    Posted by Legacy on 05/21/1999 12:00am

    Originally posted by: Yalin Wei

    ?

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: August 14, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Data protection has long been considered "overhead" by many organizations in the past, many chalking it up to an insurance policy or an extended warranty you may never use. The realities of today make data protection a must-have, as we live in a data driven society. The digital assets we create, share, and collaborate with others on must be managed and protected for many purposes. Check out this upcoming eSeminar and join eVault Chief Technology …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds