6

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

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
Steve
  • 976
  • 12
  • 12

2 Answers2

5

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.

Ciaran Keating
  • 2,793
  • 21
  • 19
  • Thanks, so there is no way to do this? I thought it would be possible because I'm already using this style: `HRESULT SendGuid( [in] GUID guid);` in many places in the idl to pass a single guid. I was really hoping to maintain the purity of the interface. Its far more contractual to avoid just using strings :( – Steve Jul 29 '11 at 13:14
  • I agree, and would prefer to do it that way myself. However, it's not possible. The reason is that ActiveX/Automation is a subset of COM, designed to make it easy for late-bound scripting languages to talk to COM objects via IDispatch. SAFEARRAY and VARIANT are part of this Automation subset, but VT_CLSID is not. Your SendGuid(GUID) method is not Automation-compatible, but it's still perfectly fine COM. Even though you don't want your interface to be Automation-compatible, as soon as you use the SAFEARRAY type you pay its price. – Ciaran Keating Jul 29 '11 at 23:52
3

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 );
erbi
  • 1,242
  • 1
  • 12
  • 14