tags:

views:

627

answers:

4

I am a COM object written in ATL that is used from a C++ application, and I want to pass an array of BYTEs between the two. My experience of COM/IDL so far is limited to passing simple types (BSTRs, LONGs, etc.).

Is there a relatively easy way to have the COM object pass an array to the caller? For example, I want to pass a raw image (TIFF) instead of messing with temporary files.

A: 

Check out using safearrays. Here's some example code:

The safearray is returned as a pointer to a VARIANT

[id(1), helpstring("LogCache")] HRESULT LogCache([out,retval] VARIANT* logCache);

Safearrays are pretty easy to use. Here's some example code which is a cache of the latest 1000 log messages of some application:

   safearray_t<bstr_t> m_logCache;
   ...
   if (m_logCache.size() > 1000)
   {
       m_logCache.pop_back();
   }

   m_logCache.push_front(Msg.str(), 0);


    variant_t LogCache()
    {
     if (!m_logCache.is_empty())
     {
      variant_t cache(m_logCache);
      return cache;
        }
    }

Note that the syntax in your case is almost certainly going to be different since I'm using the comet COM library, but the ideas/concepts are the same.

jpoh
+9  A: 

Try passing a safearray variant to the COM Object. Something like this to put a BYTE array inside a safearray variant....

bool ArrayToVariant(CArray<BYTE, BYTE>& array, VARIANT& vtResult)
{
SAFEARRAY FAR*  psarray;
SAFEARRAYBOUND sabounds[1]; 

sabounds[0].lLbound=0;
sabounds[0].cElements = (ULONG)array.GetSize();

long nLbound;

psarray = SafeArrayCreate(VT_UI1, 1, sabounds);
if(psarray == NULL)
 return false;

for(nLbound = 0; nLbound < (long)sabounds[0].cElements ; nLbound++){
 if(FAILED(SafeArrayPutElement(psarray, &nLbound, &array[nLbound]))){
  SafeArrayDestroy(psarray);
  return false;
 }
}

VariantFree(vtResult);
vtResult.vt = VT_ARRAY|VT_UI1;
vtResult.parray = psarray;
return true;
}
Aamir
Thanks - I found a very useful ATL class called CComSafeArray that makes this very easy (older versions of MFC can use COleSafeArray).
Rob
A: 

You can use BSTR to pass an array of bytes.

BYTE array[buffer_size];
...
BSTR toBePassed = SysAllocStringByteLen((OLECHAR*)array,length);
YourCOMMethod(toBePassed);
SysFreeString(toBePassed);

In your method:

BYTE* pData = (BYTE*)bstrPassed;
DWORD dataLength = SysStringByteLen(bstrPassed);
Dmitry Khalatov
DON'T DO IT. The array contents might get translated from their ANSI code page to Unicode or vice versa, causing the binary values to change. Client applications might not always know when this happens, and they will find their data corrupted.
Windows programmer
To clarify: Even though the programmer intends the array to hold binary data, various system routines don't know the programmer's intention. BSTRs are designed to hold Unicode strings, and system routines will make that assumption.
Windows programmer
A: 

SAFEARRAYs are the way to go if you want OLE-Automation compliance, and maybe use the COM interface from other languages such as VB6. But there is an alternative in IDL, for example: -

void Fx([in] long cItems, [in, size_is(cItems)] BYTE aItems[]);

This describes a method where the marshalling code can infer the number of bytes to be transfered by inspecting the value of the first parameter.

This is fine if your clients are all written in C/C++, but i think that an interface containing this would not be automation-compliant, so not usable from VB6, and possibly the standard marshaler will not be able to do the marshaling, so you'd need to generate your own proxy/stub DLL from the IDL. Not hard to do, but a bit harder than using SAFEARRAYs.

Martin