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.
class CSimpleObj : public CObject
{
DECLARE_SERIAL( CSimpleObj )
public:
CSimpleObj();
virtual ~CSimpleObj();
void SetString( CString csData );
virtual void Serialize(CArchive& ar);
void Show();
private:
CString m_strData;
};
void CSimpleObj::Serialize(CArchive& ar)
{
CObject::Serialize( ar );
if (ar.IsLoading())
{
ar >> m_strData;
}
else
{
ar << m_strData;
}
}
void CSimpleObj::Show()
{
AfxMessageBox(m_strData);
}
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 called CBlob
for it.
class CBlob
{
public:
CBlob() {};
virtual ~CBlob() {};
SAFEARRAY* Load( CObject *pObj );
BOOL Expand( CObject * &pObj, SAFEARRAY *pVar );
private:
};
SAFEARRAY* CBlob::Load( CObject *pObj)
{
CMemFile memfile;
long lMode = CArchive::store | CArchive::bNoFlushOnDelete;
CArchive ar(&memfile, lMode );
ar.m_pDocument = NULL;
ar.WriteObject(pObj);
ar.Close();
long llen = memfile.GetLength();
unsigned char *pMemData = memfile.Detach();
SAFEARRAY *psa;
psa = SafeArrayCreateVector( VT_UI1, 0, llen );
unsigned char *pData = NULL;
SafeArrayAccessData( psa, (void**)&pData );
memcpy( pData, pMemData, llen );
delete pMemData;
SafeArrayUnaccessData(psa);
return psa;
}
BOOL CBlob::Expand(CObject * &rpObj, SAFEARRAY *psa)
{
CMemFile memfile;
long lLength;
char *pBuffer;
SafeArrayAccessData( psa, (void**)&pBuffer );
lLength = psa->rgsabound->cElements;
memfile.Attach((unsigned char*)pBuffer, lLength);
memfile.SeekToBegin();
CArchive ar(&memfile, CArchive::load | CArchive::bNoFlushOnDelete);
ar.m_pDocument = NULL;
rpObj = ar.ReadObject(0);
ar.Close();
pBuffer = (char*) memfile.Detach();
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]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]SAFEARRAY(unsigned char) *pData);
};
STDMETHODIMP CBolbData::SetArray(SAFEARRAY *pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
CSimpleObj *dummy=NULL;
CBlob blob;
blob.Expand( (CObject*&)dummy, pData );
dummy->Show();
delete dummy;
return S_OK;
}
STDMETHODIMP CBolbData::GetArray(SAFEARRAY **pData)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState())
CSimpleObj *pMyOb = new CSimpleObj();
pMyOb->SetString( "A SAFEARRAY from the server!" );
CBlob blob;
*pData = blob.Load( pMyOb );
delete pMyOb;
return S_OK;
}
And finally, write a dialog based MFC application having two buttons and add the following code:
void CClientDlg::OnOK()
{
try
{
IBolbDataPtr pI( "Server.BolbData.1" );
SAFEARRAY *psa ;
pI->GetArray( &psa );
CSimpleObj *dummy=NULL;
CBlob blob;
blob.Expand( (CObject *&)dummy, psa );
dummy->Show();
delete dummy;
}
catch (_com_error e)
{
AfxMessageBox( e.ErrorMessage() );
}
}
void CClientDlg::OnLoad()
{
try
{
IBolbDataPtr pI( "Server.BolbData.1" );
SAFEARRAY *psa ;
CSimpleObj *pMyOb = new CSimpleObj();
pMyOb->SetString( "The client sent a SAFEARRAY!" );
CBlob blob;
psa = blob.Load( pMyOb );
pI->SetArray( psa );
}
catch (_com_error e)
{
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.