Using a BSTR to Transport C++ Objects Across the DCOM Interface

One problem that has frustrated me is the difficulty of building a large object on the SQL/MTS server and then using DCOM to deliver all that stuff to the client. The objective was to submit a query to the server which would then process through the database , build a complex object, that is an object with nested maps, lists and arrays of other objects, and return the whole result in usable form to the client.

The semantics of using a SAFEARRAY or a large set of COM properties is daunting to me. Since I don't need to make these complex objects available to VB or Java applications I can keep all the code in C++.

This approach takes advantage of the simple design of a BSTR which, as I understand it, is just a long pointer. What it points to is the address between a long int and a block of memory, actually the first byte of the memory block. The long int preceding the string memory contains the byte length of the memory block. Usually the memory block contains a null terminated array of 2 byte characters, but it can actually contain anything. Since COM/DCOM know how to marshall BSTRs across the network and process boundaries, anything that can be packed into a BSTR can be marshalled across.

The first step is to get the arbitrarily complex object packed into the BSTR. I used a CMemFile to receive the serialized object, then stuff the CMemFile into the BSTR.

The code looks like this:


BSTR CMyView::FetchObject(long lParam)  // pseudo-server function
{
    MyComplexClass     *p_mCC;   // a serializable class
    BSTR               bstrHoldThis;
    CMemFile           mfFile;  // works just like a real
                                // file


    CArchive  ar((CFile*)&mfFile, CArchive::store);

    // Create the complex object.  I include this stub here to indicate
    // that what is being passed is really an an object and not a pointer.
    // It could be a megabyte or more in size.

    p_mCC = new MyComplexObject(lParam);

    // A small, simple object could have been created on the stack.That's
    // not best place for a REALLY big object, although it
    // guarantees that it will go away when the function returns.
    // I generally put objects on the heap with 'new' and
    // delete them after serialization.

    // Serialize the object to the memory file
    p_mCC->Serialize(ar);

    // You need to flush the archive and file buffers
    ar.Flush();                 // flush the archive first
    mfFile.Flush();             // then the file

    // The step that creates the BSTR needs the length of the file

    ULONG lArchiveLength = mfFile.GetLength();

    // Stuff the archive into the BSTR with SysAllocStringByteLen()
    // CMemFile.Detach() returns a pointer to the memory block of the file
    // which is used to create the BSTR.

    bstrHoldThis = SysAllocStringByteLen((const char*)mfFile.Detach(),
                                            lArchiveLength);

     // Free the object.  The CMemFile will clean up after itself.

        delete p_mCC;

    // Return the BSTR result;

    return bstrHoldThis;  // send the whole thing into the aether
}

Now the caller needs to be able to unpack the BSTR and re-create the object. This is just the reverse of the steps above and I'd leave it as an exercise for the reader but I actually got it to work so here it is.


Void CMyView::OnButton1()
{
    BSTR   bstrA;
    MyComplexClass mRC;
    CMemFile   mfFile;

    // error checking omitted for clarity - but necessary

    // go get the BSTR wrapped object from the pseudo-server
    BstrA = FetchObject(m_lTestVal);

    // here's the ugly part. I back into the length of the BSTR.

    ULONG *p_lLength = (ULONG*) bstrA; // points at the byte AFTER the
    // BSTR length

    --p_lLength;    // now it points at the length of the
                    // BSTR. Like Foster's - crude but
                    // effective.

    // attach the memory part of the BSTR to the CMemFile memory block

    mfFile.Attach((unsigned char*) bstrA, *p_lLength);

    // now the object is in the memfile and it can be loaded
    CArchive ar(&mfFile, CArchive::load);

    mRC.Serialize(ar);

    // The object is now fully instantiated on the stack.
    // In this sample code it would be destroyed when this
    // function returned so you'd really want to build it with 'new'
    // on the heap and assign it to a member variable, or a
    // Document data member.

      // We still need to free the memory used by the BSTR and the
    // CMemFile. They both point to the same memory. The CMemFile
    // won't automatically free the memory that was attached when it
    // is destroyed, so freeing the BSTR memory takes care of that for us.

    SysFreeString(bstrA);
}

One could combine this method with others on a more general COM interface and agree to limit its use to MFC C++ clients (more non-portable ugliness). The challenge is to get all the serialization parts right for the array, maps, lists etc.



Comments

  • This will not really marshall

    Posted by Legacy on 07/05/2001 12:00am

    Originally posted by: Salim

    If you passed for instance complex data such as pointers, etc... This would run quickly into trouble. The reason is that COM sees the data as a string. It will marshall it as a string (for instance if sent to a machine with a different byte arrangement).
    This technique will work only on similar machines and/or for simple data types.

    Reply
  • Fix for memory leak

    Posted by Legacy on 05/11/2000 12:00am

    Originally posted by: Dick Warg

    One thing that's really wrong about this code is that it 
    
    leaks memory because of the way CMemfile.Detach() was used.

    The following code works better.

    // The step that creates the BSTR needs the length of the file

    ULONG lArchiveLength = mfFile.GetLength();

    // Stuff the archive into the BSTR with SysAllocStringByteLen()
    // CMemFile.Detach() returns a pointer to the memory block of the file
    // which is used to create the BSTR.

    byte* bp = mfFile.Detach();
    bstrHoldThis = SysAllocStringByteLen((const char*)bp,
    lArchiveLength);

    // Free the object and the memory that was obtained
    from the CMemFile.

    delete p_mCC;
    if(bp)
    delete bp;

    // Return the BSTR result;

    return bstrHoldThis; // send the whole thing into the aether
    }


    Reply
  • Using IStream

    Posted by Legacy on 03/06/2000 12:00am

    Originally posted by: Trevor Attema

    I have used IStream to solve exactly the same problem described here. 
    
    

    DCOM automatcially marshalls IStream interfaces across processes and machines and is an appropriate mechanism for passing binary data.

    You can use IStream with interfaces derived from IDispatch but don't expect them to be visible or useable in VB.

    The IDL compiler will complain with warnings which is contrary to the MS documentation which says that IStream is COM compliant.

    I personally don't approve of using BSTR's to pass binary data because the whole basis for COM is to describe a consistent interface contract. Using BSTR's for purposes other than for strings breaks the BSTR interface contract.

    Just my two cents worth.

    Reply
  • Just another way

    Posted by Legacy on 03/09/1999 12:00am

    Originally posted by: Thomas Biedermann

    Basically, I don't think that there is anything wrong with passing data via a BSTR. We are actually using this technique extensively, with a slight variation:

    Instead of defining our own classes and methods, we decided to use XML as the underlying string-structure. We just made our server component capable of dumping its data into a XML-formatted string which can be put easily into any available XML-parser (we used msxml.dll). From then on, a client component can do anything with it without the need of direct string manipulation.

    Actually, its possible to marshal a whole DOMDocument over COM, which imho will become definitely a standard way of transferring lots of data over COM boundaries.

    Reply
  • Alternate solution to the problem

    Posted by Legacy on 01/20/1999 12:00am

    Originally posted by: Paresh Mistry

    Instead of using a BSTR to transfer the contents of the CMemFile, I have used a SafeArray of VT_UI1 to achieve the same.

    This method provides ease of programming at the expense of a little overhead of using safearrays to transfer data over the wire.

    Another alternative would be to use Streams and Storages to transfer the CArchive files.

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

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds