Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Collections Interoperability

0.00/5 (No votes)
23 May 2005 1  
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here