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:

  1. Both Client and Server are written in VC++.
  2. They must be able to share object definitions (such as header files).
  3. Passing objects simplifies the application design.
  4. 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:

  1. Create an ATL DLL server.
  2. Add MFC class derived from CObject.
  3. Use DECLARE_SERIAL macro in class header.
  4. Use IMPLEMENT_SERIAL macro in class body.
  5. 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;
    }

  6. 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:

  1. Create a COM interface.
  2. Create a SAFEARRAY object.
  3. 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.
  4. 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.

Downloads

Download source files – 116 Kb

More by Author

Must Read