0

I have a COM server, done in C#, where one of the methods takes a callback function.

   [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class REDIServer
    {
        public bool Connect([MarshalAs(UnmanagedType.FunctionPtr)] ref CallBackFunction callback)
        {
            // send callback deeper into C# code
            return _service.Connect(callback);
        }

When generating tlb code out of that dll so I can use it in my C++ client, it looks like this:

  virtual HRESULT __stdcall Connect (
    /*[in,out]*/ long * callback,
    /*[out,retval]*/ VARIANT_BOOL * pRetVal ) = 0;

And finally, when I try to use the Connect() call from C++, I am using it like this:

void Callback()
{
    cout << "got a callback\n";
}

int main()
{
    HRESULT hr = CoInitialize(NULL);

    try
    {
        REDIXLServer::_REDIServerPtr ptr(__uuidof(REDIXLServer::REDIServer));

        VARIANT_BOOL ret;
        hr = ptr->Connect((long *) &Callback, &ret);
    }
    catch (_com_error & e)
    {
        cout << e.ErrorMessage() << endl;
    }

The program crashes on the Conect() call. I am getting a message box "Managed Debugging Assistant 'InvalidFunctionPointerInDelegate has detected a problem.... Invalid function pointer was passed into the runtime to be converted to a delegate...."

So what am I doing wrong? Thank you

alernerdev
  • 2,014
  • 3
  • 19
  • 35
  • These articles may be helpful. [C# C++ Interop callback](https://stackoverflow.com/q/10841081/9014308), [Changing a C# delegate's calling convention to CDECL](https://stackoverflow.com/q/5155180/9014308) – kunif Oct 15 '18 at 21:23

1 Answers1

0

First of all, if doing interop with native COM clients, I would suggest using COM events instead of callbacks

Additionally, I would suggest not using [ClassInterface(ClassInterfaceType.AutoDual)] as it generates a lot of cruft in generated tlh/tli files. Instead of that, define an interface and implement it in your class.

public delegate double FunctionCallback(int arg);

[ComVisible(true)]
[Guid("5F11C485-0AA8-461D-BB56-32D620D17311")]
public interface ITestServer
{
  double Connect([MarshalAs(UnmanagedType.FunctionPtr)] FunctionCallback callback, int arg);
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class TestServer : ITestServer
{ 
  public double Connect(FunctionCallback callback, int arg)
  {
    Console.Out.WriteLine("In server, calling callback...");
    return callback(arg);
  }  
}

You don't need to send the callback by ref (ref CallBackFunction) as you will not be changing it to point to different implementation, and since Callback is same as &Callback you can't dereference it anyway - this is why it crashes.

In your native client, callback will be modeled as long:

double Connect (long callback, long arg );

When sending a function pointer just cast it to long:

double triple(int arg) {
    return arg * 3.0;
}

double halve(int arg) {
    return arg / 2.0;
}

int main() {
    CoInitialize(NULL);
    server::ITestServerPtr svr(__uuidof(server::TestServer));
    printf("tripled: %g\n", svr->Connect((long)triple, 5));
    printf("halved: %g\n", svr->Connect((long)halve, 5));
    return 0;
}
Zdeslav Vojkovic
  • 14,391
  • 32
  • 45
  • A callback is a pointer, so you should not use `long` (32-bit in C/C++) but use pointer types if you want to properly support 32 and 64-bit binaries. – Simon Mourier Oct 16 '18 at 08:56
  • Sure, but you don't have control over interop types generated by the tooling, which define what will be declared type in .tlh/.tli files. For 32-bit components it uses `long`, so the example shows what to use in that case (it is clear from OP's example that this is 32-bit environment). For 64-bit components, `__int64` will be used, so it is safe to cast to type defined by the typelib. – Zdeslav Vojkovic Oct 16 '18 at 18:10