Passing C++ Object in ATL DLL Server





1.00/5 (3 votes)
Jun 17, 2002
2 min read

104861

838
This article explains how to pass a C++ object across a COM server.
Introduction
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 C++ Object Across ATL DLL is not difficult but of course a bit tricky and interesting too.
Before starting, make sure that your client and server both must be C++. Secondly, you must be aware of setting COM client and server.
Limitation with Interface
COM demands high degree of separation between client and server which is done through interfaces. But the problem is, interfaces offer only limited number of data types in methods. If the interface is IDispatch
based, then the choices are even more limited. Keeping these limitations, C++ object only can be passed across interfaces under these situations:
- Both client and server are written in VC++.
- They must be able to share object definitions (i.e. header files).
- Passing objects simplify 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 form
CObject
. - Use
DECLARE_SERIAL
macro in class header. - Use
IMPLEMENT_SERIAL
macro in class body. - Override the
Serialize()
method.// Your CSimpleObj class look 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; }
Next step is serializing and de-serializing (loading and storing objects) with a
CArchive
. I'm using a different class calledCBlob
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 which tells archive if 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 (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, that MIDL doesn’t recognize this data type. But the easiest way is VARIANT
type that I will discuss in the next article.
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 IDL file. - Make 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 i.e. 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 mean time, please feel free to contact me.