6
votes

I'm currently running into an issue of needing to pass a SAFEARRAY(GUID) as a return value from C++ to C#.

Currently the C# side is using an Interop dll generated from Tlbimp.exe (Type Library Importer).

The IDL is:

HRESULT GetGuids(
    [out]SAFEARRAY(GUID)* guids);

I've also tried [out, retval]

The function signature is:

HRESULT
WINAPI
MyClass::GetGuids(SAFEARRAY** guids)

If I use SafeArrayCreate() or SafeArrayCreateVector():

SAFEARRAY* psa
psa = SafeArrayCreate(VT_CLSID, 1, rgsabound);

I get a NULL SAFEARRAY pointer, which is supposed to indicate E_OUTOFMEMORY which is incorrect.

What I did find was that VT_CLSID is only for Ole property sets and not SAFEARRAY's: http://poi.apache.org/apidocs/org/apache/poi/hpsf/Variant.html Its indicated that CLSID is

I've also tried the alternate means of constructing the safe array with: SafeArrayAllocDescriptor() and SafeArrayAllocData().

hResult = SafeArrayAllocDescriptor(1, guids)
hResult = SafeArrayAllocData(*guids);

This lets me create the array, but when populating it with SafeArrayPutElement() I get an HRESULT of 0x80070057 (The parameter is incorrect). This is probably due to the fact it takes the VT_CLSID parameter as well

I can populate it manually with SafeArrayAccessData()

GUID* pData = NULL;
hResult = SafeArrayAccessData(*guids, (void**)&pData);

but I get an error from the C# side: "The value does not fall within the expected Range"

I'm not sure how to accomplish the desired functionality of returning a SAFEARRAY(GUID) to C# either by a retval or out parameter.

It seems it should be simple - there are many areas in the IDL where I'm already passing GUID's without any UDT's or marshalling. Everything works fine until I need to pass them in a SAFEARRAY.

Any help is appreciated, Thanks in advance

2

2 Answers

5
votes

You're absolutely right - the problem is that VT_CLSID isn't allowed in either VARIANT or SAFEARRAY. It boils down to GUID not being an Automation-compatible type.

I often need to do the same thing that you're trying. The easiest way around the problem is to convert the GUID to a string and then pass SAFEARRAY(VT_BSTR). It goes against the grain somewhat to do this conversion, but I suppose you could take the view that there's marshaling going on anyway and this conversion is a type of marshaling.

3
votes

The way to do it involves passing GUIDs as a UDT (user defined type).

For that, we use a SAFEARRAY of VT_RECORD elements which will be initialized with SafeArrayCreateEx. But first, we have to get a pointer to IRecordInfo that can describe the type.

Since GUID is defined in the windows/COM headers and has no uuid attached to it, we have to use something else to get an IRecordInfo interface. Basically, the two options are to create a struct that has the same memory layout as GUID in your own TypeLib, or use mscorlib::Guid defined in mscorlib.tlb

#import <mscorlib.tlb> no_namespace named_guids

IRecordInfo* pRecordInfo = NULL;
GetRecordInfoFromGuids( LIBID_mscorlib, 1, 0, 0, __uuidof(Guid), &pRecordInfo );

SafeArrayCreateEx( VT_RECORD, 1, &sab, pRecordInfo );