3

Situation: I have a managed (C#, .NET 2.0) application which uses an unmanaged (C++) DLL using P/Invoke. Along with the "simple" methods (POD arguments/return value) there's a requirement to pass a boost::variant value arrays to the code. The reason for it is that these methods pass report data (similar to Excel cells, which can be of any type). C# code accepts them as boxed "object"'s.

The previous implementation called for use of SafeArray of COM VARIANT's. However, due to poor coding/not testing it, the marshalling turned out to be leaking memory. Now I have to find another option for marshalling the data.

Previous implementation looked like this: C++:

extern "C" __declspec(dllexport) void GetReport(VARIANT& output) {
    // ... here a SafeArray of VARIANT values was created
    output.vt = VT_VARIANT | VT_ARRAY;
    output.parray = safeArray;
}

C#

[DllImport("CppLibrary.dll")]
private static extern void GetReport(out object output);

//....
object data;
GetReport(data);
object rows = data as object[];

This specific implementation leaks memory by not freeing up the interop structures.

I've tried to change the prototypes by including SafeArray marshalling directives:

C++

extern "C" __declspec(dllexport) void GetReport(SAFEARRAY output) { // Also tried SAFEARRAY*, SAFEARRAY&, VARIANT, VARIANT&, VARIANT*
    // Internal stuff
}

C#

private static extern void GetReport([Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]ref object[] output);

However, the only things I've achieved were either an empty resulting object(s), or crash due to memory corruption/stack overflow.

Problem: How to correctly marshal such data type (array of VARIANT-typed structures) to C#? I can make the C++ DLL a COM one, but this will require rewriting quite a handful of code. Is there any simpler way out of the situation? Maybe I'm missing something.

DarkWanderer
  • 8,739
  • 1
  • 25
  • 56

1 Answers1

1

There is this example: http://limbioliong.wordpress.com/2011/03/20/c-interop-how-to-return-a-variant-from-an-unmanaged-function/

In the end they use directly an IntPtr (they use it as a return value, you would have to use it as a out IntPtr), then Marshal.GetObjectForNativeVariant(), VariantClear() and Marshal.FreeCoTaskMem() from the C# side, while on the C/C++ side the VARIANT was allocated with CoTaskMemAlloc().

[DllImport("MyDLL.dll", CallingConvention = CallingConvention.StdCall)]
static extern void MyFunction(out IntPtr ptr);

[DllImport("oleaut32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
static extern Int32 VariantClear(IntPtr pvarg);

IntPtr pVariant;
MyFunction(out pVariant);

object objRet = Marshal.GetObjectForNativeVariant(pVariant);

VariantClear(pVariant);
Marshal.FreeCoTaskMem(pVariant);

pVariant = IntPtr.Zero;

Clearly you could expose another C function in your dll that frees the VARIANT (it's always correct to expose Free methods in your library, so that caller can use them and not ask himself "how should I free this memory?")

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Hmm. That seems like a viable option, I'll try it. Do you know if it works with nested variants (vt = VT_VARIANT | VT_ARRAY, as in example above)? – DarkWanderer Sep 10 '13 at 10:43
  • @DarkWanderer [VariantClear](http://msdn.microsoft.com/en-us/library/ms221165(v=vs.85).aspx) `Safearrays of variant will also have VariantClear called on each member. Using VariantClear in these cases ensures that code will continue to work if Automation adds new variant types in the future.` – xanatos Sep 10 '13 at 10:51
  • Woo-hoo! That totally nails it. I'll still have to rewrite the interface slightly, but that's nothing compared to switching to COM. Thank you very much! – DarkWanderer Sep 10 '13 at 11:28