How to make a callback to C# from C/C++ code
Almost everyone knows how to make a call to a function in an unmanaged DLL. However, sometimes we wish that we could call C# code from C/C++ code.
Imagine a scenario wherein we have a C# application which has a native C DLL called Engine.dll. There is a function entry named “
This will work just fine. However, let’s assume
Engine.dll/Main.h
Imagine a scenario wherein we have a C# application which has a native C DLL called Engine.dll. There is a function entry named “
DoWork
” in this DLL that we need to call. Calling DoWork
in the engine is as easy as making the following declaration in the C# code:C#
[DllImport("Engine.dll")]
public static extern void DoWork();
…and then using it like any other static
C# method in our C# application.This will work just fine. However, let’s assume
DoWork
is a
long-running task and we want to show a progress or so in the C# app in
order to keep our user(s) updated. To make this happen, we need to…- Define an unmanaged delegate in the C# code like –
C#
-
[UnmanagedFunctionPointer(CallingConvention.StdCall)] delegate void ProgressCallback(int value);
- Define callback signature in the C code –
C++ -
typedef void (__stdcall * ProgressCallback)(int);
- Change
DoWork
signature in C code to acceptProgressCallback
address:
C++
DLL void DoWork(ProgressCallback progressCallback)
Note: DLL is…
C++
-
#define DLL __declspec(dllexport)
- Inside the C# code, we need to create a delegate of type of the unmanaged delegate –
C# -
ProgressCallback callback = (value) => { Console.WriteLine("Progress = {0}", value); };
- Then for calling
DoWork
, we need to do it like this –
C#
-
DoWork(callback);
ProcessFile
that needs to get back to the C# in order to obtain a file path for further processing - in this case, printing its contents to the console.Engine.dll/Main.h
C++
#include "Windows.h" #ifdef __cplusplus extern "C" { #endif #define DLL __declspec(dllexport) typedef void (__stdcall * ProgressCallback)(int); typedef char* (__stdcall * GetFilePathCallback)(char* filter); DLL void DoWork(ProgressCallback progressCallback); DLL void ProcessFile(GetFilePathCallback getPath); #ifdef __cplusplus } #endif
Engine.dll/Main.c
C++
#include "Main.h" #include <stdio.h> DLL void DoWork(ProgressCallback progressCallback) { int counter = 0; for(; counter<=100; counter++) { // do the work... if (progressCallback) { // send progress update progressCallback(counter); } } } DLL void ProcessFile(GetFilePathCallback getPath) { if (getPath) { // get file path... char* path = getPath("Text Files|*.txt"); // open the file for reading FILE *file = fopen(path, "r"); // read buffer char line[1024]; // print file info to the screen printf("File path: %s\n", path ? path : "N/A"); printf("File content:\n"); while(fgets(line, 1024, file) != NULL) { printf("%s", line); } // close the file fclose(file); } }
TestApp.exe/Program.cs
C#
using System; using System.Runtime.InteropServices; using System.Windows.Forms; class Program { [UnmanagedFunctionPointer(CallingConvention.StdCall)] delegate void ProgressCallback(int value); [UnmanagedFunctionPointer(CallingConvention.StdCall)] delegate string GetFilePathCallback(string filter); [DllImport("Engine.dll")] public static extern void DoWork([MarshalAs(UnmanagedType.FunctionPtr)] ProgressCallback callbackPointer); [DllImport("Engine.dll")] public static extern void ProcessFile([MarshalAs(UnmanagedType.FunctionPtr)] GetFilePathCallback callbackPointer); [STAThread] static void Main(string[] args) { // define a progress callback delegate ProgressCallback callback = (value) => { Console.WriteLine("Progress = {0}", value); }; Console.WriteLine("Press any key to run DoWork...."); Console.ReadKey(true); // call DoWork in C code DoWork(callback); Console.WriteLine(); Console.WriteLine("Press any key to run ProcessFile...."); Console.ReadKey(true); // define a get file path callback delegate GetFilePathCallback getPath = (filter) => { string path = default(string); OpenFileDialog ofd = new OpenFileDialog() { Filter = filter }; if (ofd.ShowDialog() == DialogResult.OK) { path = ofd.FileName; } return path; }; // call ProcessFile in C code ProcessFile(getPath); } }