C# – calling C functions – C# callbacks

The final tutorial in this series on calling C functions from C# covers the problem of how to pass a C# callback function to a C function. This is needed if you are using a C DLL and there are some exported functions in the C library that require callback functions as parameters.

C# allows you to pass a C# method as a C function. This has the benefit that the C# method has access to its parent object and any data fields or other methods.

As with previous tutorials, the key to this method is to use delegates which have been marshalled to match the parameter profile of the C callback and the C function that uses the callback.

The first step is to look at some C declarations for a function that requires a callback.

typedef int (message_callback*) (char* message);
int register_message_callback(message_callback);

So, there is a type definition that declares the parameter profile of a function that can be used as a message callback. It says that the callback must take a C string (char*) as a parameter and return an int.

The second declaration is of a function that takes such a callback function as a parameter and uses it.

Lets say that the register_message_callback is declared in a C DLL which needs to be loaded into the C# program and then given a C# method as its parameter. How is this done?

The first stage is to declare a delegate that represents the type of a function with each parameter translated into an equivalent C# type:

public delegate int message_callback_delegate(string data);

This is in effect a type definition of a method with this particular parameter profile. However, this does not match the C typedef for the callback. It needs marshalling attributes:

[return: MarshalAs(UnmanagedType.I4)]
public delegate int message_callback_delegate(
   [MarshalAs(UnmanagedType.LPStr)] string data);

The LPStr marshalling attribute maps the C# string onto a char* C parameter.

I can then write a message callback in C# with the same parameter profile as the delegate just declared:

public int message_callback(string message)
{
    ...
}

The next stage is to declare a delegate type for the function that registers the callback:

private delegate int register_message_callback_t(message_callback_delegate callback);

This then also needs to have marshalling attributes added to match up the calling conventions and type profiles of the C function:

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.I4)]
private delegate int register_message_callback_t(
    [MarshalAs(UnmanagedType.FunctionPtr)] message_callback_delegate callback);

Now it is possible to use the dynamic loader written in a previous article to load the C library, load the register function and then call it:

dynaloader loader = new dynaloader("mylibrary.dll");
register_message_callback_t register_message_callback =
    loader.load_function("register_message_callback");
register_message_callback(message_callback);

So, the first line loads the DLL into memory. The second loads the register function into memory and associates the function with the variable register_message_callback. This is of the delegate type register_message_callback_t which is effectively a pointer to the function, with strong typing onto the correct parameter profile. The third line calls this function, passing the name of our C# method message_callback as its parameter. This C# method will then be used by the DLL as its message callback.

Leave a Reply