Search code examples
c#c++nativemarshalling

Marshalling safearray of safearray from VARIANT in C++ to C#


I'm trying to call a function in .NET from a native library, like this:

typedef int (WINAPI* TGetAllObjects)(int hModel, VARIANT& objects);

If you call it in the C++ client, the debugger shows that objects are a safearray of VARIANT, and each VARIANT is a safearray of BSTR.

I tried to implement as follows in C#:

public delegate TRetCode TGetAllObjects(int hModel, 
    [Out,In,MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT )] ref MyStruct[] objects);

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
    [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
    public string[] Objects;
}

When calling the method, it gives an error:

System.Runtime.InteropServices.SafeArrayTypeMismatchException: "Specified array was not of the expected type."

Please tell me how to properly organize data marshalling in such a situation.


Solution

  • Try :

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    delegate int TGetAllObjects(int hModel, ref object objects); 
    

    and see EXACTLY what you receive. The objects variable is a VARIANT, not a SAFEARRAY. The VARIANT& objects contains the SAFEARRAY, it isn't a SAFEARRAY.

    Then inside your code you can check its type and cast it.

    Ah and note the CallingConvention (WINAPI is StdCall).

    I've made some tests and it seems the basic VARIANT "container" is passed correctly. I'll say the truth, I consider a SAFEARRAY of VARIANT that are SAFEARRAYs of BSTR a weapon that should be included in the banned list by the Geneva Convention.

    Some more tests make me think that my solution is the easyest one. I wasn't able to accept/pass directly a object[] or a string[], and the received objects is a object[] where each element is a string[]. Something like:

    var objects = new object[] { new string[] { "A", "B" }, new string[] { "C", "D" } };
    

    Your "casting" problem

    SAFEARRAY can have the index of the first element != 0 (this to support VB that uses 1-based arrays, arrays, where the first element is 1). .NET does support this in the category "strange arrays". Multidimensional arrays (string[1, 2]) and non-0 based arrays are in this category, and they are a pain to handle. Non-zero based arrays more so.

    Code to convert the array in your case (where with "convert" I mean: copy it to a standard .NET array of arrays):

    object[] castedObjects = (object[])objects;
    string[][] strings = new string[castedObjects.Length][];
    
    for (int i = 0; i < castedObjects.Length; i++)
    {
        Array ar = (Array)castedObjects[i];
        string[] ar2 = new string[ar.Length];
        Array.Copy(ar, ar2, ar2.Length);
        strings[i] = ar2;
    }