I need to communicate with a program from my C# code. I have received a communication library from the company that made the program, along with a quite detailed explanation on how to integrate with the application. I feel I have done everything correctly, the C++ functions all return success statusses, but the most important part, the callback function, is never invoked.
I cannot use [DllImport(...)]
, I have to use LoadLibrary
, GetProcAddress
and Marshal.GetDelegateForFunctionPointer
to load the library into memory and call it's functions. This is so I keep an instance of the library in memory, so it remembers the callback function. This is because the callback function is registered and then used by subsequent requests to communicate additional information.
I'm keeping a reference to the callback function so the GC doesn't collect it and it isn't available for the C++ code.
When I wait for the callback, nothing happens. I don't get errors, I don't see anything specific happening, it just lets me wait forever. The callback doesn't happen.
I'm hoping that somebody can tell me what I have missed, because I don't see the solution anymore.
class Program
{
private static Callback keepAlive;
static void Main(string[] args)
{
try
{
var comLib = LoadLibrary(@"C:\BIN\ComLib.dll");
var isInstalledHandle = GetProcAddress(comLib, nameof(IsInstalled));
var isInstalled = Marshal.GetDelegateForFunctionPointer<IsInstalled>(isInstalledHandle);
var isInstalledResult = isInstalled("activation-key"); // returns success
Console.WriteLine("Is installed result: " + isInstalledResult);
var registerCallbackHandle = GetProcAddress(comLib, nameof(RegisterCallback));
var registerCallback = Marshal.GetDelegateForFunctionPointer<RegisterCallback>(registerCallbackHandle);
keepAlive = CallbackFunc;
var registerResult = registerCallback(keepAlive); // returns success
Console.WriteLine("Register result: " + registerResult);
var initHandle = GetProcAddress(comLib, nameof(Init));
var init = Marshal.GetDelegateForFunctionPointer<Init>(initHandle);
var initResult = init("ERP_INTEGRATION", "activation-key"); // returns success
Console.WriteLine("Init result: " + initResult);
Console.WriteLine("Wait...");
Console.ReadLine();
FreeLibrary(comLib);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private static int CallbackFunc(int id, string data)
{
Console.WriteLine($"{id} > {data}");
return 0;
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int IsInstalled(string registryKey);
private delegate int Callback(int id, string data);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int RegisterCallback(Callback callback);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int Init(string id, string command);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool FreeLibrary(IntPtr hModule);
}
EDIT 1: I got back from the developers of the software. Apparently, because I'm doing all the work on the main thread, the app can't call back because the thread is busy. I should offload the app (and the callback method) to another thread so the app is free to call the callback funcion. Unfortunately, I have no idea how to do this.
EDIT 2: On request, I put the code in a WinForms app and the callback works nicely there. I do not fully understand why, except that the callback must happen while a) the main thread is free (waiting for input from the form) or b) it offloads the callback to another thread. I have no idea how to simulate either in a console app.