4

I'm having trouble converting a C++ .dll function to C#.

The function is this:

    void funct(void*(*handler)(void*));

I think this means passing a pointer to function taking a void pointer and returning a void pointer, as explained here:

Passing Function Pointer.

What I'm trying to do is do the same thing in C#, but I have do idea how. I tried to use delegates, but I am both unsure how to and also if they can even do what I am trying to do.

Thanks for the help!

EDIT:

Here are the C++ functions for register_message_handler and message_handler:

void register_message_handler(void*(*handler)(void*));
void *message_handler(void *raw_message);

EDIT: xanatos has the exact explanation and conversion to C# below. Thanks a ton xanatos!

Community
  • 1
  • 1
Tropicana55
  • 43
  • 1
  • 7
  • No, it returns void. So it is a method with no parameters and no return value. – xanatos Mar 27 '15 at 12:18
  • see this http://www.codeproject.com/Articles/27298/Dynamic-Invoke-C-DLL-function-in-C or http://stackoverflow.com/questions/13280323/calling-a-dll-function-that-contains-a-function-pointer-from-c-sharp – Yanshof Mar 27 '15 at 12:19
  • 2
    void funct(Action handler) – Biscuits Mar 27 '15 at 12:19
  • Do you mean that you want to call a C++ function which is in a dll from C# code and need to define a signature for platform invoke? If so, please show the calling code. – Codor Mar 27 '15 at 12:20
  • 2
    void*(handler)(void) should be void (*handler)(void) – Serve Laurijssen Mar 27 '15 at 12:24
  • @ServéLaurijssen - probably, but the OP should really clarify or correct. `()` do matter a whole lot in C declarations. – H H Mar 27 '15 at 12:25
  • Sorry, I've been trying to use all of the recommendations. The posted code is exactly how it is posted (with the added '*'): void*(handler)(void) – Tropicana55 Mar 27 '15 at 12:28
  • 1
    @Tropicana55 Can you show us the C (the ones in the .h file) signature of `register_message_handler` and `message_handler`? It isn't very clear how they should work together – xanatos Mar 27 '15 at 12:50
  • @Tropicana55 I've update the answer given what I think you want – xanatos Mar 27 '15 at 12:51

1 Answers1

8
void funct(void*(*handler)(void*));

is a function that accepts a pointer and returns a pointer.

In C# it would be:

IntPtr MyFunc(IntPtr ptr);

So you'll need a delegate like:

public IntPtr delegate MessageHandlerDelegate(IntPtr ptr);

[DllImport("mydll.dll")]
public static extern void register_message_handler(MessageHandlerDelegate del);

Note that P/Invoke (calling native methods) is one of the rare cases where Action<...> and Func<...> delegates don't work, and you have to build "specific" delegate.

BUT to call it, it's a little complex, because you must save a "copy" of the delegate so that if the C functions calls this method at any time, this copy is still "alive" and hasn't been GC (see for example https://stackoverflow.com/a/5465074/613130). The most common way to do it is to encapsulate everything in a class, and put the copy in a property/field of the class:

class MyClass
{
    public delegate IntPtr MessageHandlerDelegate(IntPtr ptr);

    [DllImport("mydll.dll")]
    public static extern void register_message_handler(MessageHandlerDelegate del);

    [DllImport("mydll.dll")] 
    public static extern IntPtr message_handler(IntPtr message);

    public MessageHandlerDelegate Del { get; set; }

    public void Register()
    {
        // Make a copy of the delegate
        Del = Handler;
        register_message_handler(Del);            
    }

    public IntPtr Handler(IntPtr ptr)
    {
        // I don't know what ptr is
        Console.WriteLine("Handled");
        return IntPtr.Zero; // Return something sensible
    }
}

Note that if you use IntPtr then you don't need the unsafe.

If you want to pass message_handler to register_message_handler the safest way is to

// Make a copy of the delegate
Del = message_handler;
register_message_handler(Del);

There is a possibility that you can do directly no there isn't, checked

register_message_handler(message_handler);

and that the CLR will solve this, BUT I'm not sure of this... I can't easily test it, and I wouldn't do it. (if you want to test it, add a GC.Collect() just after the register_message_handler. If after some time you receive a CallbackOnCollectedDelegate error then you know you can't do it :-) )

Mmmh... checked. You can't do the raw register_message_handler(message_handler), you have to use MyClass.

Be very aware of something: it's better to always specify the calling convention C-side and C#-side even in function pointers. C# uses stdcall, while C uses cdecl. In x86 mode you can get very awful silent crashes (in x64 there is a single calling convention)

void __stdcall register_message_handler(void* (__stdcall *handler)(void*));
void * __stdcall message_handler(void *raw_message);

and C# side

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr MessageHandlerDelegate(IntPtr ptr);

[DllImport("Win32Project1.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void register_message_handler(MessageHandlerDelegate del);

[DllImport("Win32Project1.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr message_handler(IntPtr message);

(or everywhere cdecl)

Community
  • 1
  • 1
xanatos
  • 109,618
  • 12
  • 197
  • 280
  • The question is about `void *(f)(void)` , none of your examples is an exact match. – H H Mar 27 '15 at 12:22
  • Holy cow! Thanks a ton xanatos! Sorry for the returning void or void pointer confusion, I forgot to turn the c++ function into code and the * dropped out. I just edited in the 2 c++ functions and I will try your recommendation now. – Tropicana55 Mar 27 '15 at 12:56
  • Hey xanatos, I implemented your code and I had another question as to how to call it correctly later. In addition to the code inside of MyClass above, I added in the message_handler function: `[DllImport("mydll.dll")] public unsafe static extern void message_handler(void *message);` Then, I tried calling it later on with this call: `MyClass.register_message_handler(MyClass.message_handler);` and I got a "convert from void* to IntPtr" error. I tried to convert the void* using the IntPtr() conversion call, but I think I'm missing the point of the error. – Tropicana55 Mar 27 '15 at 13:17
  • 1
    @Tropicana55 The signature is wrong. Right one: `[DllImport("mydll.dll")] public static extern IntPtr message_handler(IntPtr message)`. And see the added paragraphs to the answer. When you see a `void*`, use `IntPtr` – xanatos Mar 27 '15 at 13:20
  • Oh that's right, I'm sorry. You did mention that void* to IntPtr point. Thank you so much! It now works perfectly. I wish I could do more than just thank you; hell, I can't even upvote you because I'm too new. But you have been an incredible help. – Tropicana55 Mar 27 '15 at 13:40