65.9K
CodeProject is changing. Read more.
Home

Collections Interoperability

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.18/5 (5 votes)

May 24, 2005

2 min read

viewsIcon

41315

downloadIcon

990

This article describes how to move collections between native and managed code.

Introduction

In my work, I am using a COM object as an interface between a managed server and a HW device that has a C DLL interface. Last week, I encountered the need to pass a collection from the HW device to the server. To do that, I had the COM read it from the device and pass it to the server. The problem was that I couldn’t return a collection of my UDT (User Defined Type) from COM to the managed server.

The code

So how do we return a UDT from native to managed code?

The easiest way to achieve interoperability between native and managed code is by putting the unmanaged code in a COM object. So what is left is to return the collection from COM to the managed code.

COM has two ways to return collections:

  1. Return a C style array like so:
    HRESULT Foo(int Length, [length_is(Length)]  SUDTStruct  Col[]);
  2. Return a SAFEARRAY with the collection like so:

    If you use IDL, the syntax is:

    HRESULT Foo(SAFEARRAY(SUDTStruct) **Col)

    If you use embedded IDL, the syntax is:

    HRESULT Bar([out, satype(struct PTZPresetsInfo))] SAFEARRAY **Col)

The first way does not work with .NET. The interop proxy that will be created for it will look like:

void Foo(int Length, ref  SUDTStruct  Col)

which means that only one struct will be returned and not the whole collection, so the only way is using a safe array.

To do that we need a few simple stages:

  1. Define the UDT you want to pass in the collection.
  2. Define a GUID for the UDT:
             [export,
             uuid("3DA0FCDB-BAC1-4b13-8808-AB3E43A281BA")]
             struct SUDTStruct  
             {
                     ...
             };
  3. Define a get function:
             HRESULT Bar(([out, satype(struct PTZPresetsInfo)) SAFEARRAY **Col)
  4. Implement the get function by creating a SAFEARRAY and returning it:
             SAFEARRAY *pSafeArrayCol;
             unsigned int ndim =  1;
     
             USES_CONVERSION;
             SAFEARRAYBOUND  rgbounds;
             rgbounds.lLbound = 0;
             rgbounds.cElements = ColSize;
            
             IRecordInfo*                  pRecInfo = NULL;
     
             ITypeLib* pTypelib = NULL;
             HRESULT hr = LoadTypeLib(A2OLE(“UDT TYPE LIBRARY”),&pTypelib);
             if (FAILED(hr))
             {
                     return NULL;
             }
     
             ITypeInfo *pTypeInfo;
             hr = 
               pTypelib->GetTypeInfoOfGuid(__uuidof(CollectionType::value_type), 
                                                                    &pTypeInfo);
             if (FAILED(hr))
             {
                     return NULL;
             }
     
             hr = GetRecordInfoFromTypeInfo(pTypeInfo, &pRecInfo);
             if (FAILED(hr))
             {
                     return NULL;
             }
     
             pSafeArrayCol = SafeArrayCreateEx(VT_RECORD, 1, 
                                               &rgbounds, pRecInfo);
             pRecInfo->Release();
     
             CollectionType::value_type *pItem;
             hr = SafeArrayAccessData(pSafeArrayCol, 
                     reinterpret_cast<PVOID*>(&pItem));
  5. Init the collection pointed to by pItem:
             SafeArrayUnaccessData(pSafeArrayCol);

A few remarks: if you want to pass a collection of a UDT to COM all you need to do is define a UDT with a GUID and define a function like so:

         HRESULT Bar(SAFEARRAY([in] SUDTStruct) *Col)

This function will appear in the .NET interop like so:

        void Bar(System.Array Col)

When calling it, pass a SUDTStruct[] as the collection:

In order to ease the process of creating the safe array I created a simple template function that receives a STL collection that has the UDT as its value type and create a safe array out of it with the same data. The source is attached to this article.

For example:

std::vector<SUDTStruct> Col;
 
// init the collection with the data
SAFEARRAY *pSafeArrayCol = CreateUDTSafeArrayFromCol(Col);

The CreateSafeArray function will create a safe array and initialize it with the data in the STL collection.

The demo project attached is a COM object and a .NET assembly that sends and gets collections from it.

Points of Interest

Microsoft has done a lot of work to make COM a mechanism for interoperability between managed and unmanaged areas, but not all the COM capability is exported through the .NET proxy created for COM.