Click here to Skip to main content
6,595,444 members and growing! (20,935 online)
Email Password   helpLost your password?
Languages » C# » COM Interop     Intermediate

Collections Interoperability

By Meir Bechor

This article describes how to move collections between native and managed code.
C#, VC7.1, .NET, WinXP, COM, VS.NET2003, Dev
Posted:23 May 2005
Views:23,030
Bookmarked:15 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
5 votes for this article.
Popularity: 2.92 Rating: 4.18 out of 5

1

2

3
2 votes, 40.0%
4
3 votes, 60.0%
5

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

About the Author

Meir Bechor


Member
6 years C++ and .NET programer in Nice Systems
Occupation: Web Developer
Location: Israel Israel

Other popular C# articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 1 of 1 (Total in Forum: 1) (Refresh)FirstPrevNext
GeneralCorrection in LoadTypeLib code and Issue With pSafeArrayCol [modified] PinmemberPRISMAY4:49 19 Jun '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 23 May 2005
Editor: Smitha Vijayan
Copyright 2005 by Meir Bechor
Everything else Copyright © CodeProject, 1999-2009
Web17 | Advertise on the Code Project