不从 C++ 代码调用 C# 回调函数

The C# callback function is not called from C++ code

我需要通过我的 C# 代码与程序通信。我从制作该程序的公司那里收到了一个通信库,以及关于如何与应用程序集成的非常详细的解释。我觉得我做的一切都正确,C++函数所有return成功状态,但最重要的部分,回调函数,从未被调用。

我不能使用 [DllImport(...)],我必须使用 LoadLibraryGetProcAddressMarshal.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 它按预期工作。