Collections Interoperability
May 24, 2005
3 min read
VC7.1
WinXP
VS.NET2003
C++
C#
Windows
.NET
Visual-Studio
Dev
COM
Intermediate

by Meir Bechor
Contributor
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:
- Return a C style array like so:
HRESULT Foo(int Length, [length_is(Length)] SUDTStruct Col[]);
- Return a
SAFEARRAY
with the collection like so:If you use IDL, the syntax is:
CODEHRESULT 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:
- Define the UDT you want to pass in the collection.
- Define a GUID for the UDT:
[export, uuid("3DA0FCDB-BAC1-4b13-8808-AB3E43A281BA")] struct SUDTStruct { ... };
- Define a get function:
HRESULT Bar(([out, satype(struct PTZPresetsInfo)) SAFEARRAY **Col)
- 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));
- Init the collection pointed to by
pItem
:CODESafeArrayUnaccessData(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:
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)