Question

I'm trying to send a COM object over a MSMQ message in C++. This is my object :

class ATL_NO_VTABLE CAnalisis :
    public CComObjectRootEx,
    public CComCoClass,
    public ISupportErrorInfo,
    public IDispatchImpl,
    public IPersistStreamInit
{
private:
    typedef struct {
        DOUBLE size;
        float color;
        float light;

        BSTR imgName;

        BSTR  uname;

    } Image;

    Image img;
    STDMETHOD(Load)(IStream *pStm);
    STDMETHOD(Save)(IStream *pStm,  BOOL fClearDirty);

Everything goes fine and I can get the whole object but the BSTR types. Floats and integers are properly sent and received. But the BSTR types dont work. I'm trying to send strings and can't find the way. I did try with VARIANT instead and the result was wrong too. Somehow, it looks like the strings are not serialized.

These are some of the get and set functions for my ATL component:

This one works fine:

STDMETHODIMP CAnalisis::getLight(FLOAT* light)
{

    *light=img.light;
    return S_OK;
}

STDMETHODIMP CAnalisis::setLight(FLOAT light)
{
    img.light=light;
    return S_OK;
}

This one doesn't :

STDMETHODIMP CAnalisis::getImgName(BSTR* imgName)
{
    *imgName = img.imgName;

    return S_OK;
}

STDMETHODIMP CAnalisis::setImgName(BSTR imgName)
{

    img.imgName=imgName;
    return S_OK;
}

and this is the way I create the MSMQ message and fill the values in my producer :

// For these ActiveX components we need only smart interface pointer
        IMSMQQueueInfosPtr  pQueueInfos; 
        IMSMQQueueInfoPtr   pQueueInfo; 
        IMSMQQueuePtr       pQueue;
        IUnknownPtr         pIUnknown;
        // Instanciate the follwing ActiveX components
        IMSMQQueryPtr       pQuery(__uuidof(MSMQQuery));
        IMSMQMessagePtr     pMessage(__uuidof(MSMQMessage));


        IAnalisisPtr pAnalisis(__uuidof(Analisis));

                WCHAR *  imagen;        
        imagen = L"imagen1.jpg";
                pAnalisis->setImgName(imagen);


                 (...)

                pAnalisis->setFruitSize(20.00);

                 (...)

                pQueueInfo = new IMSMQQueueInfoPtr( __uuidof(MSMQQueueInfo) );

        pQueueInfo->PathName = "MYCOMPUTER\\private$\\myprivatequeue";

            pQueue = pQueueInfo->Open(MQ_SEND_ACCESS, MQ_DENY_NONE);
        pMessage->Body = static_cast(pAnalisis);
                pMessage->Send(pQueue);


here is the serialization code

STDMETHODIMP CAnalisis::Load( IStream *pStm )
{
    ULONG           cb;
    HRESULT         hr;
    if (NULL==pStm)
        return ResultFromScode(E_POINTER);
    // Read an object from the stream.
    //
    hr=pStm->Read(&img, sizeof(Image), &cb);
    if (FAILED(hr))
        return hr;
    if (sizeof(Image) != cb)
        return E_FAIL;

    return NOERROR;
}

STDMETHODIMP CAnalisis::Save( IStream *pStm, BOOL bClearDirty )
{
    ULONG           cb;
    HRESULT         hr;
    if (NULL==pStm)
        return ResultFromScode(E_POINTER);

    // Write an object into the stream.
    hr=pStm->Write(&img, (ULONG)sizeof(Image), &cb);
    if (FAILED(hr) || sizeof(Image)!=cb)
       return ResultFromScode(STG_E_WRITEFAULT);

    return NOERROR;
}

If I get the BSTR value in the producer (before serialization), pAnalisis-getImgName(), it works fine. In contrast, when I try to get it in the consumer, after reading the message from the queue, it doesn't return anything. The other values, such as the size, are returned with no trouble.

does anyone know how to send a BSTR value inside a COM object through MSMQ ?

I've tried to find some similar examples but totally in vain.

the thing is that i'm getting either an very weird value with weird characters or an Hexadecimal value, depending on how I extract the value .. the thing is that I do never get the right value.

and I was wondering, however... are we sure it is possible to send a BSTR value ? if i'm not wrong, it is a pointer to a string... I'm running two different processes (i.e. producer and consumer), so they use different memory blocks, and they are meant to be run on different machines so...

I was trying to send this info as a VARIANT type.. but also got lost. However, this seems a bit less far-fetched than sending a BSTR.

ANY IDEAS ON THIS?

Was it helpful?

Solution

The problem is that the serialization of Image class treats it as a contiguous block of memory. Since BSTR is really a pointer only the pointer value is serialized and the BSTR payload is lost.

Instead you should write all fields except BSTRs as binary and process BSTRs separately. For example, you can write BSTR length as integer first, then its payload. When reading you will read length first, call SysAllocStringLen() to allocate a buffer, then read the payload.

Leave serialization of simple fields as it is (the IPersistStreamInit::Save() ):

pStm->Write(&(img.color), (ULONG)sizeof(float), &cb);

For BSTRs do this:

int length = SysStringLen( img.uname );
pStm->Write(&length, (ULONG)sizeof(int), &cb);
if( length > 0 ) {
   pStm->Write( img.uname, (ULONG)(length * sizeof(WCHAR) ), &cb);
}

Similar for reading (the IPersistStreamInit::Load()):

int length;
pStm->Read(&length, (ULONG)sizeof(int), &cb);
if( length > 0 ) {
   img.uname = SysAllocStringLen( 0, length );
   pStm->Read( img.uname, (ULONG)( length * sizeof( WCHAR) ), &cb);
} else {
   img.uname = 0;
}

Note that this code writes/reads string length and then writes/reads the payload that consists of Unicode characters. Unicode characters occupy more than one bytes each - hence the multiplication in IStream Read/Write methods call.

OTHER TIPS

If you simply pass a WCHAR -- the length information is lost. The BSTR is malformed and this is probably causing you all the grief. You need to use SysAllocString to use it across components. See MSDN -- the Remarks section. Try:

BSTR imagen = SysAllocString(L"imagen1.jpg");

OK, this answer depends on you doing something odd these days, but might be applicable. A long, long time ago I had to pass a VB string under VB6 and VC++6(pro) from a VB app. to a VC++ app. The length came through OK but I often received one character on the other side.

The problem was that the receiving app. was not compiled for unicode, but rather as an ANSI project. The COM layer code that unpacked it on the far side of transfer did an interesting trick that I only found documented in an obscure corner of the MSDN in a book excerpt: it created an ABSTR.

An ABSTR isn't actually a type. There is no way to declare one. It's actually a reformatting of a BSTR's underlying storage so that you can pretend it's an ASCII char* in C++. This is done by first making sure the BSTR points to the first character after its header (typical anyway for such structures in C++, IIRC) and then shuffling the actual string data so that it contains all of the first bytes followed by all of the second bytes. Another name for this is "pure evil".

Two very bad things can happen this way: if this conversion is done, and you treat the result as if it's still a wide character string, you get gibberish. If it's not done and you treat the results as an ASCII character array, you usually get a single character, if the original contained only ASCII-range characters, since in the wide representation every other byte is a zero and the high bytes come second.

I can't quite tell from your description if this is what happened to you. But I recommend stopping the thing in a debugger and looking at all of the string data under that received value to see if it's been reshuffled in some unexpected way. If it has been shuffled, ask yourself why and look at the way you built the project.

The fact of an almost undocumented format wedged into an existing type as an alternative memory layout that's really hard to find, even by the how-many-string-formats-can-we-make-up standards of MS development, just about made me scream. It was almost as bad as trying to identify "GetModuleFileName" for the first time as the function to use to get the current executable's path.

Your object needs to create a copy of the string in both the getter and the setter:

STDMETHODIMP CAnalisis::getImgName(BSTR* imgName)
{
    *imgName = SysAllocString(img.imgName);

    return S_OK;
}

STDMETHODIMP CAnalisis::setImgName(BSTR imgName)
{
    SysFreeString(img.imgName);
    img.imgName=SysAllocString(imgName);
    return S_OK;
}

of course, you need to free the string in the destructor, check for NULL ptrs etc.

Alternatively you can use CComBSTR instead of BSTR. CComBSTR is a smarter than BSTR, it takes care of allocating and deallocating the memory.

My suggestion is to put your fields in a Variant (even if temporarily) then use Variant streaming code to flatten the data, and deserialize it on the other end.

Here's streaming code you can use (sorry it's about 20 years old :) )

Link: https://github.com/kasajian/VariantStream/blob/master/VariantStream.h

The code is a bit verbose to paste in here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top