How to pass a delegate or function pointer from C# to C++ and call it there using InternalCall
Asked Answered
L

2

19

I have the following setup in C#:

public delegate void CallbackDelegate(string message);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern void setCallback(CallbackDelegate aCallback);

public void testCallbacks()
{
    System.Console.Write("Registering C# callback...\n");
    setCallback(callback01);
}

public void callback01(string message)
{
    System.Console.Write("callback 01 called: " + message + "\n");
}

And this in C++ (the function is registered correctly via mono_add_internal_call ):

typedef void (*CallbackFunction)(const char*);
void setCallback(MonoDelegate* delegate)
{
    // How to convert the MonoDelegate to a proper function pointer?
    // So that I can call it like func("test");
}

The C++-function is called and something is passed to the delegate variable. But what now?

I looked around and found the function "mono_delegate_to_ftnptr" mentioned a few times, and from those examples it seems to be exactly what I need.
However, this function simply does not seem to exist in my distribution of mono (4.6), so I can only guess it does not exist any more.

I also found a few examples of how to do this with PInvoke. Which is something I do not want to use - since InternalCall is much faster for my purpose.
Of course, if PInvoke would be the only way, so be it, but I doubt that.

In the end, I am really at a loss at how to proceed from here.

Langland answered 30/9, 2016 at 11:56 Comment(2)
Any specific reason for the downvote? Should I add something?Langland
In the Microsoft toolchain implementation of InternalCall, C++/CLI, you'd use the signature setCallback(System::Action<System::String^>^ delegate) to directly accept a .NET delegate that directly receives a .NET string. Perhaps if you showed how InternalCall functions are passed .NET reference types in Mono, it could lead to a lighterweight solution.Denni
L
20

After some more hours of digging I finally found a (the?) solution.
Basically, what works for the PInvoke approach works here as well, you can pass a function pointer instead of a delegate from C# to C(++).
I'd prefer a solution where you can pass a delegate directly, but you can always add some wrapper code in C# to at least make it look like that.

Solution:

C#:

public delegate void CallbackDelegate(string message);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern void setCallback(IntPtr aCallback);

private CallbackDelegate del; 
public void testCallbacks()
{
    System.Console.Write("Registering C# callback...\n");
    del = new CallbackDelegate(callback01);
    setCallback(Marshal.GetFunctionPointerForDelegate(del));

    System.Console.Write("Calling passed C++ callback...\n");
}

public void callback01(string message)
{
    System.Console.Write("callback 01 called. Message: " + message + "\n");
}

C++:

typedef void (*CallbackFunction)(MonoString*);
void setCallback(CallbackFunction delegate)
{
    std::cout << &delegate << std::endl;
    delegate(mono_string_new(mono_domain_get(), "Test string set in C++"));
}

Watch out, though: You need to keep the delegate around in C# somehow (which is why I assigned it to "del") or it will be caught by the GC and your callback will become invalid.
It makes sense, of course, but I feel this is easy to forget in this case.

Langland answered 1/10, 2016 at 6:9 Comment(1)
I've tried this solution out with my own library, and .NET seems to call the callback function twice, causing an access violation. Why is this happening?Estelleesten
H
2

You can pass a function pointer as a parameter in C++ to C# using intptr_t.

MSDN isn't accurate, below code works:

In C++:

static void func(int param)
{
    //...
}  
    
void other_func()
{
    ptr->SetCallback( reinterpret_cast<intptr_t>(func));
}

In C#:

public static mydelegatetype somefunc = null;
    
public void SetCallback(IntPtr function_pointer)
{
    somefunc = (mydelegatetype)
    Marshal.GetDelegateForFunctionPointer(function_pointer, typeof(mydelegatetype));
}
Hatchery answered 29/3, 2019 at 6:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.