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.