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



Comments

  • passing dialog box to DLL in VC++

    Posted by sowmya_a on 10/24/2005 07:28am

    hello,
    I want to pass the dialog box to DLL which is created in exe so that I can display an image on that dialog box inside DLL and return the same to exe

    Reply
  • Sorry sir ! But what's the use ????

    Posted by Legacy on 10/01/2003 12:00am

    Originally posted by: bhuppi

    hello sir !
    The 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

    Reply
  • how to

    Posted by Legacy on 03/13/2003 12:00am

    Originally 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.
    also 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 .

    Reply
  • The Solution by Virendra is GOOD!!!

    Posted by Legacy on 08/31/2002 12:00am

    Originally posted by: amit

    The Solution by Virendra is really helpfull.
    It becomes very easy by using interfaces to pass Objects
    Thanks Virendra
    - Amit

    Reply
  • love that COM

    Posted by Legacy on 06/27/2002 12:00am

    Originally 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
    http://lightning.prohosting.com/~twoencor

    Reply
  • Use C++ Interface class to pass C++ Objects in InProcServer

    Posted by Legacy on 06/27/2002 12:00am

    Originally posted by: Virendra Prasad

    The above method requires both client and server to have the implementation of the C++ class. Also using serialization you are creating a copy of the C++ object.  above method is ok if you want to send serialized data, but if you want to reuse the same object then it will not work and also it will be slow due to serialize and de serialize.
    
    

    COM is based on interface. COM interface is similar to C++ abstract class. So to use pass a C++ object you can pass a C++ abstract base class for that C++ object. To do this define an abstract base class for the C++ class and then typecast the pointer to long. On client side re type cast the long value to abstract base class pointer and use it.

    Also since you you sharing this class in both COM and client app, you should move this class to a shared DLL


    -----------foo.h-------------------
    // abstract class
    class CFooInterface
    {
    public:

    virtual void doSomething()=0;
    };
    -----------------------------------
    ----------- fooimp.h --------------
    // imp class
    class CFooImp: public CFooInterface
    {
    public:
    virtual void doSomething()
    {
    // ....
    }
    };
    -----------------------------------


    ----------- COM ------------

    STDMETHODIMP CComXYZ::GetFoo(long *pValue)
    {
    *pValue = (long) m_foo; // m_foo is CFooImp
    return S_OK;
    }

    -----------------------------------
    ----------- Client ------------

    long lValue;
    spXYZ->GetFoo(&lValue);

    CFooInterface *pFoo = (CFooInterface*)lValue;
    pFoo->doSomething();


    -----------------------------------


    This will only work for inproc servers.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • A global data storage provider whose business is booming needed a best-in-class data center to serve as the backbone of its technical operations going forward—and it needed it delivered within a year.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds