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 “
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:
[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 –
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
delegate void ProgressCallback(int value);
- Define callback signature in the C code –
typedef void (__stdcall * ProgressCallback)(int);
- Change
DoWork
signature in C code to accept ProgressCallback
address:
DLL void DoWork(ProgressCallback progressCallback)
Note: DLL is…
#define DLL __declspec(dllexport)
- Inside the C# code, we need to create a delegate of type of the unmanaged delegate –
ProgressCallback callback =
(value) =>
{
Console.WriteLine("Progress = {0}", value);
};
- Then for calling
DoWork
, we need to do it like this –
DoWork(callback);
Here is a sample source code for a simple application. This code snippet includes a second scenario wherein we have a function in C code called
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
#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
#include "Main.h"
#include <stdio.h>
DLL void DoWork(ProgressCallback progressCallback)
{
int counter = 0;
for(; counter<=100; counter++)
{
if (progressCallback)
{
progressCallback(counter);
}
}
}
DLL void ProcessFile(GetFilePathCallback getPath)
{
if (getPath)
{
char* path = getPath("Text Files|*.txt");
FILE *file = fopen(path, "r");
char line[1024];
printf("File path: %s\n", path ? path : "N/A");
printf("File content:\n");
while(fgets(line, 1024, file) != NULL)
{
printf("%s", line);
}
fclose(file);
}
}
TestApp.exe/Program.cs
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)
{
ProgressCallback callback =
(value) =>
{
Console.WriteLine("Progress = {0}", value);
};
Console.WriteLine("Press any key to run DoWork....");
Console.ReadKey(true);
DoWork(callback);
Console.WriteLine();
Console.WriteLine("Press any key to run ProcessFile....");
Console.ReadKey(true);
GetFilePathCallback getPath =
(filter) =>
{
string path = default(string);
OpenFileDialog ofd =
new OpenFileDialog()
{
Filter = filter
};
if (ofd.ShowDialog() == DialogResult.OK)
{
path = ofd.FileName;
}
return path;
};
ProcessFile(getPath);
}
}
Enjoy it