0

I'm using P/Invoke to call an unmanaged C function from C#, passing an array of objects. In the unmanaged code I query IUnknown for IDispatch. This works for the simple case, but getting IDispatch fails if one of the objects is an array itself.

managed code:

    [DllImport("NativeDll.dll")]
    static extern void TakesAnObjectArray(int len, 
        [MarshalAs(UnmanagedType.LPArray, 
         ArraySubType = UnmanagedType.IUnknown)]object[] a);

    public static void exec1(int a, object b, string c)
    {
        Object[] info_array;
        Object[] parameters_array;

        parameters_array = new object[4];
        parameters_array[0] = a;
        parameters_array[1] = b;
        parameters_array[2] = c;
        parameters_array[3] = 55;
        // THIS WORKS GREAT
        TakesAnObjectArray(4, parameters_array);

        info_array = new object[6];
        info_array[0] = parameters_array;
        // THIS DOESN'T
        // I CAN'T GET IDISPATCH FOR THE 1ST 'OBJECT'
        TakesAnObjectArray(6, info_array);
    }

unmanaged code:

    void TakesAnObjectArray(int len, LPUNKNOWN p[])
    {
        HRESULT hr;
        for (int i=0; i<len; i++)
        {
            IDispatch *disp = NULL;
            hr = p[i]->QueryInterface(IID_IDispatch, (void**)&disp);
        }
    }

QueryInterface is successful most of the time. But, if the managed object is actually a 'System.Object[]', I can't get an IDispatch interface (hr = 0x80004002 = E_NOINTERFACE = 'No such interface supported').

Can I use MarshalAs(...) in some way that will fix this? Or Is there another way to get this to work?

zvikam
  • 66
  • 8

1 Answers1

3

Kind of interesting to see that CLR managed to make the first part of the snippet work at all. It probably managed to generate an IDispatch pointer because the System.Array type is [ComVisible(true)]. So you can get QI to work but there isn't anything you can actually do with the IDispatch pointer without having intimate knowledge of the Array class members. You ran out of luck on the 2nd snippet because there isn't anything in the declaration that forces the CLR to marshal the array element.

You'll need to fix this the right way, an array is not a IDispatch object in COM automation. The default COM marshaling for Object[] is SAFEARRAY*, a safe array whose elements are VARIANT. Change the TakesAnObjectArray argument type from LPUNKNOWN[] to SAFEARRAY*. And change the pinvoke declaration to just plain object[] without the [MarshalAs] attribute.

You'll need to use SafeArrayLock() and SafeArrayGetElement() in your C++ code to access the array elements. The second snippet requires an extra indirection because the first element is a VARIANT that contains a SAFEARRAY itself.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • thank you for the comment. I actually managed to do quite a lot with the IUnknown: got an IDispatch, got ITypeInfo, called typeInfo->GetDocumentation() to get the type name. Then used 'GetIDsOfNames' and 'Invoke' to execute 'ToString' and even got the right information... I'll try the SAFEARRAY approach, see if it works better. – zvikam Apr 25 '12 at 11:57
  • Cool. Marshaling as SAFEARRAY worked, and I got all the elements of the main array as well as the sub-array. I even got a VARIANT of VT_UNKNOWN for complex objects which I can query for further information. – zvikam Apr 25 '12 at 13:01