Passing C++ Object in ATL DLL
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.

Comments
passing dialog box to DLL in VC++
Posted by sowmya_a on 10/24/2005 07:28amSorry sir ! But what's the use ????
Posted by Legacy on 10/01/2003 12:00amOriginally posted by: bhuppi
hello sir !
ReplyThe technique which you have described is really wonderful. And it's a very good work around to pass objects in COM.
But don't you think we loss the purpose of COM itself , if our COM component will support only VC++ and it won't be able to use in other than C++ environment ? i.e. here we will be having restriction on the class of it's client.
Is there any other way around to pass object such that it can even be accessible in other than C++ environment as well.
Thanks & regards,
Bhuppi
how to
Posted by Legacy on 03/13/2003 12:00amOriginally posted by: sanjay samuel
uttam can you please send me an example how to use lists in atl com and how the client can be passed the list so that he can add and delete from the list.
Replyalso it would be very useful if you can send me the code for opening a document file say some (file.doc) using com
the help would be very useful .
The Solution by Virendra is GOOD!!!
Posted by Legacy on 08/31/2002 12:00amOriginally posted by: amit
The Solution by Virendra is really helpfull.
ReplyIt becomes very easy by using interfaces to pass Objects
Thanks Virendra
- Amit
Use C++ Interface class to pass C++ Objects in InProcServer
Posted by Legacy on 06/27/2002 12:00amOriginally posted by: Virendra Prasad
Replylove that COM
Posted by Legacy on 06/27/2002 12:00amOriginally posted by: Pharaoh
whats the purpose of using COM, if you must a header in both client/server :P, that limitation is great,im glad it solved your problem however!, check out .NET, i believe they solve this problem i myself just have my feet wet in it.
Pharaoh
Replyhttp://lightning.prohosting.com/~twoencor