【问题标题】:C# C++ Interop callbackC# C++ 互操作回调
【发布时间】:2012-05-31 20:50:52
【问题描述】:

我最近一直在修补 C# 到 C++ 的互操作,特别是设置一个从 C++ DLL 调用的回调函数。

namespace TomCSharpDLLImport
{
    class Program
    {
        public delegate void TomDelegate(int a, int b);

        [DllImport("TomDllNative.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void GetData();

        [DllImport("TomDllNative.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void SetCallback(TomDelegate aCallback);

        static void Main(string[] args)
        {
            TomDelegate lTD = new TomDelegate(Program.TomCallback);

            SetCallback(lTD); //Sets up the callback

            int thread = Thread.CurrentThread.ManagedThreadId;

            GetData(); //This calls the callback in unmanaged code

            while (true) ;
        }

        //Callback function which is called from the unmanaged code
        public static void TomCallback(int a, int b)
        {
            Console.WriteLine("A: {0} B: {1}", a, b);
            int thread = Thread.CurrentThread.ManagedThreadId;
        }
    }
}

我的问题是,当程序控制进入 TomCallback 函数时,我希望它会在 Main 中运行 while(true) 循环。然而,程序只是退出。我无法完全理解这种行为,我的一部分人认为这是预期的,但我的一部分人会预计它会继续主要。

我所期待的……

  1. 调用 GetData() 函数
  2. GetData 函数调用回调
  3. 回调函数返回GetData
  4. GetData 返回到 main()

然而这并不完全正确。

有人能解释一下发生了什么吗?

为了节省空间,我没有发布非托管代码,但是如果需要,我很乐意发布

编辑: 我打开了非托管调试(完全忘了这样做),现在我看到了崩溃..

运行时检查失败 #0 - ESP 的值未在函数调用中正确保存。这通常是用一个调用约定声明的函数和一个用不同调用约定声明的函数指针调用的结果。

本地代码,因为这是崩溃的地方

#include "stdafx.h"
typedef void (*callback_function)(int, int);

extern "C" __declspec(dllexport) void SetCallback(callback_function aCallback);
extern "C" __declspec(dllexport) void GetData();

callback_function gCBF;

__declspec(dllexport) void SetCallback(callback_function aCallback)
{
    gCBF = aCallback;
}

__declspec(dllexport) void GetData()
{
    gCBF(1, 2);
}

【问题讨论】:

    标签: c# c++ callback interop


    【解决方案1】:

    您必须使用

    IntPtr Marshal.GetFunctionPointerForDelegate(Delegate d)
    

    方法。

    您使用带有 System.Delegate 作为参数的 SetCallback() 是错误的。

    做起来

    SetCallback(Marshal.GetFunctionPointerForDelegate(lTD));
    

    并将 SetCallback 重新声明为

    /// StdCall is crucial here
    [DllImport("TomDllNative.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern void SetCallback(IntPtr aCallback);
    

    【讨论】:

    • 您好维克多,感谢您的回复。我按照您的建议进行了更改,但它仍然表现出完全相同的行为。
    • 程序“退出” - 这意味着崩溃。你会这么好心显示崩溃转储吗?这可以在事件查看器工具的应用程序错误列表中完成。或者只是复制'n'粘贴程序的控制台输出。
    • 试试“stdcall”调用约定!你应该早点发布日志 :) 我已经编辑了 StdCall 的答案。
    • 是的,我很抱歉,我忘了打开“调试非托管代码”。不过还是解决了,非常感谢您的帮助!现在是我去阅读调用约定之间的区别的时候了:)
    【解决方案2】:

    我遇到了与 OP 完全相同的问题(相同的错误消息)。接受的答案根本没有帮助,甚至@TomP89 在 cmets 中也承认了这一点。

    这并不奇怪,因为错误消息表明传递回调函数的调用约定是错误的,而假设的解决方案改变了传递回调函数的调用约定,如果库导出的是 cdecl 函数,则会导致堆栈不平衡(Win32 API 之外的所有函数都是默认情况)。

    事实证明,.Net 默认假定任何委托的调用约定“stdcall”。通常,非托管代码将采用与其导出函数相同的调用约定(在本例中和我的:“cdecl”)。

    所以真正的解决方案(最终对我有用)是将回调的调用约定更改为“cdecl”。简而言之,question 展示了这是如何实现的:

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void TomDelegate(int a, int b);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-19
      • 2012-02-23
      • 2010-10-19
      • 1970-01-01
      相关资源
      最近更新 更多