不从 C++ 代码调用 C# 回调函数
The C# callback function is not called from C++ code
我需要通过我的 C# 代码与程序通信。我从制作该程序的公司那里收到了一个通信库,以及关于如何与应用程序集成的非常详细的解释。我觉得我做的一切都正确,C++函数所有return成功状态,但最重要的部分,回调函数,从未被调用。
我不能使用 [DllImport(...)]
,我必须使用 LoadLibrary
、GetProcAddress
和 Marshal.GetDelegateForFunctionPointer
将库加载到内存中并调用它的函数。这是为了让我在内存中保留库的一个实例,以便它记住回调函数。这是因为回调函数被注册,然后被后续请求用来传递额外信息。
我保留了对回调函数的引用,因此 GC 不会收集它并且它不可用于 C++ 代码。
当我等待回调时,没有任何反应。我没有收到错误,我没有看到任何具体的事情发生,它只是让我永远等待。回调没有发生。
我希望有人能告诉我我错过了什么,因为我再也看不到解决方案了。
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);
}
编辑 1: 我从软件开发人员那里回来了。显然,因为我在主线程上完成所有工作,应用程序无法回调,因为线程正忙。我应该将应用程序(和回调方法)卸载到另一个线程,以便应用程序可以自由调用回调函数。不幸的是,我不知道该怎么做。
编辑 2: 根据要求,我将代码放在 WinForms 应用程序中,回调在那里工作得很好。我不完全理解为什么,除了回调必须在 a) 主线程空闲(等待表单输入)或 b) 将回调卸载到另一个线程时发生。我不知道如何在控制台应用程序中进行模拟。
查看对此的回答:C# C++ Interop callback。在将委托传递给库之前尝试将委托转换为本机函数指针。
我找到了一个有点老套的答案,但它确实有效。我将控制台应用程序转换为 WinForms 应用程序。这使我可以访问 System.Windows.Forms.Application.DoEvents
。这使我可以启动一个 Task
,它会在进程外运行并不断刷新需要处理的事件。这允许外部程序调用回调函数。
private bool _callbackOccured = false;
static async Task Main(string[] args)
{
try
{
var comLib = LoadLibrary(@"C:\BIN\ComLib.dll");
await Task.Run(async () => {
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);
while (!_callbackOccured)
{
await Task.Delay(100);
Appliction.DoEvents();
}
FreeLibrary(comLib);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private static int CallbackFunc(int id, string data)
{
Console.WriteLine($"{id} > {data}");
_callbackOccured = true;
return 0;
}
编辑:它不能完全与 Task.Run
一起使用,我必须使用实际的 Thread.Start
才能使其正常工作。 Task
被杀得太快了,我仍然遇到同样的问题,外部应用程序在它做任何有用的事情之前就被关闭了,但是 Thread
它按预期工作。
我需要通过我的 C# 代码与程序通信。我从制作该程序的公司那里收到了一个通信库,以及关于如何与应用程序集成的非常详细的解释。我觉得我做的一切都正确,C++函数所有return成功状态,但最重要的部分,回调函数,从未被调用。
我不能使用 [DllImport(...)]
,我必须使用 LoadLibrary
、GetProcAddress
和 Marshal.GetDelegateForFunctionPointer
将库加载到内存中并调用它的函数。这是为了让我在内存中保留库的一个实例,以便它记住回调函数。这是因为回调函数被注册,然后被后续请求用来传递额外信息。
我保留了对回调函数的引用,因此 GC 不会收集它并且它不可用于 C++ 代码。
当我等待回调时,没有任何反应。我没有收到错误,我没有看到任何具体的事情发生,它只是让我永远等待。回调没有发生。
我希望有人能告诉我我错过了什么,因为我再也看不到解决方案了。
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);
}
编辑 1: 我从软件开发人员那里回来了。显然,因为我在主线程上完成所有工作,应用程序无法回调,因为线程正忙。我应该将应用程序(和回调方法)卸载到另一个线程,以便应用程序可以自由调用回调函数。不幸的是,我不知道该怎么做。
编辑 2: 根据要求,我将代码放在 WinForms 应用程序中,回调在那里工作得很好。我不完全理解为什么,除了回调必须在 a) 主线程空闲(等待表单输入)或 b) 将回调卸载到另一个线程时发生。我不知道如何在控制台应用程序中进行模拟。
查看对此的回答:C# C++ Interop callback。在将委托传递给库之前尝试将委托转换为本机函数指针。
我找到了一个有点老套的答案,但它确实有效。我将控制台应用程序转换为 WinForms 应用程序。这使我可以访问 System.Windows.Forms.Application.DoEvents
。这使我可以启动一个 Task
,它会在进程外运行并不断刷新需要处理的事件。这允许外部程序调用回调函数。
private bool _callbackOccured = false;
static async Task Main(string[] args)
{
try
{
var comLib = LoadLibrary(@"C:\BIN\ComLib.dll");
await Task.Run(async () => {
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);
while (!_callbackOccured)
{
await Task.Delay(100);
Appliction.DoEvents();
}
FreeLibrary(comLib);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private static int CallbackFunc(int id, string data)
{
Console.WriteLine($"{id} > {data}");
_callbackOccured = true;
return 0;
}
编辑:它不能完全与 Task.Run
一起使用,我必须使用实际的 Thread.Start
才能使其正常工作。 Task
被杀得太快了,我仍然遇到同样的问题,外部应用程序在它做任何有用的事情之前就被关闭了,但是 Thread
它按预期工作。