Environment: ATL, COM, MFC
Introduction
A few weeks ago, I was desperately looking for a sample in which a C++ object can be sent across a COM interface but could not get any sample for the same. That’s why I decided to post this article here.
Passing a C++ object across an ATL DLL is not difficult, but, of course, it is a bit tricky and interesting too.
Before starting the project, make sure that your client and server are both be C++-compliant. Secondly, you must be aware of setting your COM client and server.
Limitations with the Interface
COM demands a high degree of separation between the client and server; this is done through interfaces but the problem is that interfaces offer only a limited number of data types in methods. If the interface is IDispatch-based, the choices are even more limited. Keeping these limitations in mind, C++ objects only can be passed across the interface under these situations:
- Both Client and Server are written in VC++.
- They must be able to share object definitions (such as header files).
- Passing objects simplifies the application design.
- Your application may need to run in a distributed environment. You want COM’s capabilities of remote activation, local/remote transparency, and security.
I would recommend that, before starting your work, you should also refresh the Serialization topic first.
Now, let’s continue with the sample and do the following:
- Create an ATL DLL server.
- Add MFC class derived from CObject.
- Use DECLARE_SERIAL macro in class header.
- Use IMPLEMENT_SERIAL macro in class body.
- Override the Serialize() method.
// Your CSimpleObj class looks like this:
class CSimpleObj : public CObject
{
DECLARE_SERIAL( CSimpleObj )
public:
// constructor and destructor
CSimpleObj();
virtual ~CSimpleObj();
// Set internal string data
void SetString( CString csData );
// Used to serialize data into an archive
virtual void Serialize(CArchive& ar);
// Display the data string
void Show();
private:
CString m_strData;// Internal data string
};// Write this object to an archive
void CSimpleObj::Serialize(CArchive& ar)
{
CObject::Serialize( ar );if (ar.IsLoading())
{
// extract data from archive
ar
>> m_strData;
}
else
{
// store data into archive
ar << m_strData;
}
}// Method to display data in this object
void CSimpleObj::Show()
{
AfxMessageBox(m_strData);
}// save a string in data member
void CSimpleObj::SetString(CString csData)
{
m_strData = csData;
} - Now, the next step is Serializing and De-Serializing (Loading and Storing Objects) with a CArchive. I’m using a different class called CBlob for it.
class CBlob
{
public:
CBlob() {};
virtual ~CBlob() {};// Extract data from a CObject and load it into a SAFEARRAY.
SAFEARRAY* Load( CObject *pObj );
// Re-create an object from a SAFEARRAY
BOOL Expand( CObject * &pObj, SAFEARRAY *pVar );
private:
};// Extract data from a CObject and use it to create a SAFEARRAY.
SAFEARRAY* CBlob::Load( CObject *pObj)
{
CMemFile memfile; // memory file// define the flag that tells the archive whether it should
// load or store
long lMode = CArchive::store | CArchive::bNoFlushOnDelete;
// create the archive using the memory file
CArchive ar(&memfile, lMode );// m_pDocument is not used
ar.m_pDocument = NULL;
// serialize the object into the archive
ar.WriteObject(pObj);
// close the archive — the data is now stored in memfile
ar.Close();// get the length (in bytes) of the memory file
long llen = memfile.GetLength();// detach the buffer and close the file
unsigned char *pMemData = memfile.Detach();// set up safearray
SAFEARRAY *psa;// create a safe array to store the stream data
psa = SafeArrayCreateVector( VT_UI1, 0, llen );// pointers to byte arrays
unsigned char *pData = NULL;// get a pointer to the safe array. Locks the array.
SafeArrayAccessData( psa, (void**)&pData );// copy the memory file into the safearray
memcpy( pData, pMemData, llen );// clean up buffer
delete pMemData;// unlock access to safearray
SafeArrayUnaccessData(psa);// return a pointer to a SAFEARRAY allocated here
return psa;
}// Re-create an object from a SAFEARRAY
BOOL CBlob::Expand(CObject * &rpObj, SAFEARRAY *psa)
{
CMemFile memfile; // memory file for de-serailze
long lLength; // number of bytes
char *pBuffer; // buffer pointer// lock access to array data
SafeArrayAccessData( psa, (void**)&pBuffer );// get number of elements in array. This is the number of bytes
lLength = psa->rgsabound->cElements;// attach the buffer to the memory file
memfile.Attach((unsigned char*)pBuffer, lLength);// start at beginning of buffer
memfile.SeekToBegin();// create an archive with the attached memory file
CArchive ar(&memfile, CArchive::load | CArchive::bNoFlushOnDelete);// document pointer is not used
ar.m_pDocument = NULL;// inflate the object and get the pointer
rpObj = ar.ReadObject(0);// close the archive
ar.Close();// Note: pBuffer is freed when the SAFEARRAY is destroyed
// Detach the buffer and close the file
pBuffer = (char*) memfile.Detach();// release the safearray buffer
SafeArrayUnaccessData( psa );return TRUE;
}
I’m using SAFEARRAY here because this is the best selection for our use. It can contain some complex multidimensional arrays, but for this example we are only using a very simple array. There’s one problem with SAFEARRAY data: MIDL doesn’t recognize this data type, but the easiest way is to use the VARIANT type that I will discuss in next article.
The next steps are as follows:
- Create a COM interface.
- Create a SAFEARRAY object.
- Define [helpstring(“method SetArray”)] HRESULT SetArray([in]SAFEARRAY (unsigned char) pData);[helpstring(“method GetArray”)] HRESULT GetArray([out/*,retval*/]SAFEARRAY(unsigned char) *pData); in an IDL file.
- Make a MFC-based client to test the application.
Your IDL file should look like this:
interface IBolbData : IUnknown
{
[helpstring(“method SetArray”)] HRESULT SetArray([in]SAFEARRAY
(unsigned char) pData);
[helpstring(“method GetArray”)] HRESULT GetArray([out/*,retval*/]
SAFEARRAY(unsigned char) *pData);
};// Sets object.
STDMETHODIMP CBolbData::SetArray(SAFEARRAY *pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())// create a dummy pointer of CSimpleObj
CSimpleObj *dummy=NULL;
// create blob obect to expand/deserialize
CBlob blob;
// Init dummy object using safe array through this function
blob.Expand( (CObject*&)dummy, pData );
dummy->Show(); // Call show function to test the object.
delete dummy; // Delete the pointer.return S_OK;
}// Creates Object and sends to client.
STDMETHODIMP CBolbData::GetArray(SAFEARRAY **pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
// create object to send to server
CSimpleObj *pMyOb = new CSimpleObj();// set the string data
pMyOb->SetString( “A SAFEARRAY from the server!” );// create blob to serialize object
CBlob blob;// load the object into the blob
*pData = blob.Load( pMyOb );
// delete the pMyOb pointer
delete pMyOb;return S_OK;
}
And, finally, write a dialog-based MFC application having two buttons and add the following code:
void CClientDlg::OnOK()
{
// create COM smart pointer from CLSID string
try
{
IBolbDataPtr pI( “Server.BolbData.1” );
SAFEARRAY *psa ;// Get the safearray from the server
pI->GetArray( &psa );// create a pointer to an object
CSimpleObj *dummy=NULL;// blob object to expand
CBlob blob;// use the blob to expand the safearray into an object
blob.Expand( (CObject *&)dummy, psa );
// call a method on the object to test it
dummy->Show();// delete the object
delete dummy;
}
// Handle any COM exceptions from smart pointers
catch (_com_error e)
{
// display the message string from the error
AfxMessageBox( e.ErrorMessage() );
}
}void CClientDlg::OnLoad()
{
try
{
// create smart pointer from CLSID string
IBolbDataPtr pI( “Server.BolbData.1” );
SAFEARRAY *psa ;// create object to send to server
CSimpleObj *pMyOb = new CSimpleObj();// set the string data
pMyOb->SetString( “The client sent a SAFEARRAY!” );// create blob to serialize object
CBlob blob;// load the object into the blob
psa = blob.Load( pMyOb );// delete the object
delete pMyOb;pI->SetArray( psa );
}
// Handle any COM exceptions from smart pointers
catch (_com_error e)
{
// display the message string from the error
AfxMessageBox( e.ErrorMessage() );
}
}
Conclusion
This article covers a number of topics; for example, how to use Serialization, how to use SAFEARRAY, and how to pass a C++ object across the interface. I also would like to say thanks to William Rubin, whose article helped me quite a lot. I was planning to explain this topic in a bit more detail but due to shortage of time I couldn’t do so but will keep updating the document from time to time. In the meantime, please feel free to contact me.