Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Passing C++ Object in ATL DLL Server

0.00/5 (No votes)
16 Jun 2002 1  
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