Invoke .NET method via COM, with a ref array

-1

I have a .NET DLL that exposes a method that looks like this in C#:

void TestMethod(ref TestClass[] parameter)

Where TestClass is a regular C# class that has a COM-visible interface, and I'm supposed to call the method with an array with one element. ​I can't figure out how to invoke this via COM from a C++ program.

That parameter becomes a SAFEARRAY**. Currently, I'm doing this, where m_Object is a smart pointer to the class that has TestMethod:

VARIANT* current = NULL;
HRESULT hrx = SafeArrayAccessData(ret, (LPVOID*)&current);
ITestClassPtr item;
item.CreateInstance(__uuidof(TestClass));
current->vt = VT_UNKNOWN;
current->punkVal = item.GetInterfacePtr();
SafeArrayUnaccessData(ret);
HRESULT hr = (*m_Object)->TestMethod(&ret);

​This results in 0x80131533 "A mismatch has occurred between the runtime type of the array and the sub type recorded in the metadata".

I have also tried:

ITestClassPtr item;
item.CreateInstance(__uuidof(TestClass));
long i = 0;
SafeArrayPutElement(ret, &i, item);
HRESULT hr = (*m_Object)->TestMethod(&ret);

Which results in an unhelpful 0x80131600.

I've also tried using a CComSafeArray, but I'm getting the same results (it's just a wrapper around SAFEARRAY anyway).

I do not have sources for the DLL, and I can't debug into it. I've searched around the web, and I can find lots of examples for how to invoke a C# DLL via COM, but none that covers specifically how to pass ref arrays of classes from C++ to C#.

Any ideas?

c#
c++
com
asked on Stack Overflow Jun 23, 2020 by F. Polo

1 Answer

0

Let's suppose you have these C# classes:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class TestClass1
{
    public void SayHello()
    {
        Console.WriteLine("Hello 1");
    }

    public void TestMethod(ref TestClass2[] parameter)
    {
        parameter[0].SayHello();
    }
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class TestClass2
{
    public void SayHello()
    {
        Console.WriteLine("Hello 2");
    }
}

You can register it and create the type library with a command line like this:

%windir%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe ClassLibrary1.dll /codebase /tlb

If you look at the typelib created using OleView from Windows SDK, TestMethod is declared like this:

[id(0x60020005)]
HRESULT TestMethod([in, out] SAFEARRAY(_TestClass2*)* parameter);

So its a SAFEARRAY of _TestClass2 pointers (IUnknown-derived), and you can create it and use it like this (assuming Visual Studio support):

...
#import "c:\mypath\ClassLibrary1.tlb" raw_interfaces_only

using namespace ClassLibrary1;


int main()
{
  CoInitialize(NULL);
  {
    // warning: all error checks omitted

    CComPtr<_TestClass1> tc1;
    tc1.CoCreateInstance(__uuidof(TestClass1));
    tc1.SayHello(); // outputs "Hello 1"

    CComPtr<_TestClass2> tc2;
    tc2.CoCreateInstance(__uuidof(TestClass2));

    SAFEARRAY* psa = SafeArrayCreateVector(VT_UNKNOWN, 0, 1);

    LONG index = 0;
    // note this will call AddRef on tc2 so it's safe
    SafeArrayPutElement(psa, &index, (_TestClass2*)tc2);

    tc1->TestMethod(&psa); // Outputs "Hello 2"

    SafeArrayDestroy(psa);
  }

  CoUninitialize();
  return 0;
}
answered on Stack Overflow Jun 24, 2020 by Simon Mourier

User contributions licensed under CC BY-SA 3.0