Click here to Skip to main content
15,881,803 members
Articles / Desktop Programming / ATL
Article

Passing C++ Object in ATL DLL Server

Rate me:
Please Sign up or sign in to vote.
1.00/5 (3 votes)
16 Jun 20022 min read 104K   838   35   10
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:

  1. Both client and server are written in VC++.
  2. They must be able to share object definitions (i.e. header files).
  3. Passing objects simplify 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 form 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 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 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 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:

  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 IDL file.
  4. 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) Barclays Wealth
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 1 Pin
xxbbcc29-Jan-12 4:38
xxbbcc29-Jan-12 4:38 
QuestionMemory leak? Pin
dicker1-Aug-05 0:10
dicker1-Aug-05 0:10 
Do not need "SafeArrayDestroy"?
GeneralYou are defenitly not a com expert! Pin
RJH24-Jun-02 10:25
RJH24-Jun-02 10:25 
GeneralYet another hack Pin
24-Jun-02 3:02
suss24-Jun-02 3:02 
GeneralRe: Yet another hack Pin
Womble25-Jun-02 0:49
Womble25-Jun-02 0:49 
GeneralRe: Yet another hack Pin
axel6681-Sep-03 2:57
axel6681-Sep-03 2:57 
GeneralRe: Yet another hack Pin
Trying to Get There...9-Jul-04 5:28
Trying to Get There...9-Jul-04 5:28 
GeneralRe: Yet another hack Pin
08098-Jan-05 3:02
08098-Jan-05 3:02 
AnswerRe: Yet another hack Pin
mfpgomes14-Mar-07 2:20
mfpgomes14-Mar-07 2:20 
GeneralNaughtly boy Pin
Giles17-Jun-02 9:37
Giles17-Jun-02 9:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.