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.