【问题标题】:Marshaling C/C++ function with callback function in parameter to C#在 C# 的参数中使用回调函数编组 C/C++ 函数
【发布时间】:2016-06-10 19:53:29
【问题描述】:

我的本​​机代码中有以下内容:

typedef void (__stdcall * HandlerCallBack)(float);

class ASSIMP_API NewProgressHandler : public ProgressHandler
{
    HandlerCallBack CallBack;
public:
    bool Update(float percentage = -1.f){
        if (CallBack) CallBack (percentage);
        return true;
    }

    void SetCallBack (HandlerCallBack callback) {
        CallBack = callback;
    }
};

void Importer::SetProgressHandlerCallBack (HandlerCallBack CallBack) {
   NewProgressHandler* pNewHandler = new NewProgressHandler ();
   pNewHandler->SetCallBack (CallBack);
   ProgressHandler* pHandler = pNewHandler;
   SetProgressHandler (pHandler);
}

我需要打电话

SetProgressHandlerCallBack (HandlerCallBack CallBack)

来自 c# 代码,所以我执行以下操作:

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void HandlerCallBack([MarshalAs(UnmanagedType.R4)]float percent);

[DllImport("my.dll")]
public static extern void SetProgressHandlerCallBack ([MarshalAs(UnmanagedType.FunctionPtr)] 
                                                      HandlerCallBack callBack);

public void AssimpCallBack ([MarshalAs(UnmanagedType.R4)]float percent) {
    Debug.Log (percent);
}



void SomeFunction () {
   HandlerCallBack cb = new HandlerCallBack (AssimpCallBack);
   SetProgressHandlerCallBack (cb);
}

但我遇到了一个例外,

SetProgressHandlerCallBack

at(包装器托管到本机) ModellInstantiator:SetProgressHandlerCallBack (ModellInstantiator/HandlerCallBack)

我不知道我错过了什么。

【问题讨论】:

  • 您看到的异常是什么?
  • 您认为 C++\CLI 包装器可能吗?这是一种非常干净和自然的方式来规避所有声明式编组的内容。
  • SetProgressHandlerCallBack at (wrapper managed-to-native) ModellInstantiator:SetProgressHandlerCallBack (ModellInstantiator/HandlerCallBack) 这就是我能看到的。

标签: c# c++ callback marshalling


【解决方案1】:

在您显示的SetProgressHandlerCallback 函数上方的代码中,您的代码如下所示:

void Importer::SetProgressHandlerCallBack(...) { ... }

这有两个问题,这两个问题都可以用一个简单的最小完整可验证示例来解释,我创建了1一个新的 Visual Studio 解决方案,其中包含一个 C# (WinForms) 项目和 C++ Dll(非托管非 C++/CLI)。

在 DLL (dllmain.cpp) 中,我放置了以下代码:

typedef void (__stdcall *HandlerCallBack)(float);

extern "C" {

    // extern "C" ignored for C++ classes
    class __declspec(dllexport) TestClass
    {
    public:
        static void CallMeBack(HandlerCallBack callback)
        {
            callback(1.0f);
        }
    };


    void __declspec(dllexport) CallRightBack(HandlerCallBack callback)
    {
        TestClass::CallMeBack(callback);
    }
}

您可以看到与您正在使用的回调签名相匹配的回调签名,以及一个静态类方法和一个导出函数。构建它,启动 Visual Studio 命令提示符并运行

dumpbin /exports <dllname>.dll

这为您提供了从 DLL 中导出的函数,如下所示:

(Trimmed to just show what we care about)
    ordinal hint RVA      name

          1    0 00011082 ??4TestClass@@QEAAAEAV0@$$QEAV0@@Z = @ILT+125(??4TestClass@@QEAAAEAV0@$$QEAV0@@Z)
          2    1 000110E1 ??4TestClass@@QEAAAEAV0@AEBV0@@Z = @ILT+220(??4TestClass@@QEAAAEAV0@AEBV0@@Z)
          3    2 00011069 ?CallMeBack@TestClass@@SAXP6AXM@Z@Z = @ILT+100(?CallMeBack@TestClass@@SAXP6AXM@Z@Z)
          4    3 00011014 CallRightBack = @ILT+15(CallRightBack)

您会注意到,常规全局函数以其未修饰的名称导出,但静态方法以 修饰(损坏)名称导出。

现在让我们向 C# 项目添加一些代码,看看为什么会出现问题。添加 2 个按钮(button1 和 button22)。在表单中,我添加了以下代码:

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
delegate void HandlerCallbackDelegate(float value);

[DllImport("CppDllTestProj.dll")]
static extern void CallRightBack([MarshalAs(UnmanagedType.FunctionPtr)]HandlerCallbackDelegate callback);

[DllImport("CppDllTestProj.dll")]
static extern void CallMeBack([MarshalAs(UnmanagedType.FunctionPtr)]HandlerCallbackDelegate callback);

HandlerCallbackDelegate callbackDel;

// in the designer associated with button1
private void button1_Click(object sender, EventArgs e)
{
    if (this.callbackDel == null)
    {
        this.callbackDel = new HandlerCallbackDelegate(this.Callback);
    }
    CallRightBack(callbackDel);
}

// in the designer associated with button2
private void button2_Click(object sender, EventArgs e)
{
    if (this.callbackDel == null)
    {
        this.callbackDel = new HandlerCallbackDelegate(this.Callback);
    }
    CallMeBack(callbackDel);
}

void Callback(float value)
{
    MessageBox.Show(value.ToString());
}

编译,将 dll 复制到 C# 项目的构建目录中,然后运行。点击button1,回调被调用,消息框出现。点击按钮2,程序崩溃并出现异常:

无法在 DLL“CppDllTestProj.dll”中找到名为“CallMeBack”的入口点。 在 ScratchApp.Form1.CallMeBack(HandlerCallbackDelegate 回调) 在 Form1.cs:line 49 中的 ScratchApp.Form1.button2_Click(Object sender, EventArgs e)

无法找到名为“CallMeBack”的入口点意味着它在该 DLL 中找不到具有该名称的函数。如果我现在将 C# 测试应用程序中的 CallMeBack 定义更改为:

[DllImport("CppDllTestProj.dll", EntryPoint="?CallMeBack@TestClass@@SAXP6AXM@Z@Z")]
static extern void CallMeBack([MarshalAs(UnmanagedType.FunctionPtr)]HandlerCallbackDelegate callback);

重新编译并重新启动,现在两个按钮都可以正常工作了。 (您的损坏/装饰名称可能看起来不同。使用您从垃圾箱获得的任何值。)

这个问题的根源在于 C++ 类和类成员忽略了 extern "c" 存储类说明符3

解决上述问题后您将遇到的另一个问题是以下代码:

void SomeFunction () {
   HandlerCallBack cb = new HandlerCallBack (AssimpCallBack);
   SetProgressHandlerCallBack (cb);
}

您会注意到,在控制从对SetProgressHandlerCallBack 的调用返回的那一刻,cb 变得可用于垃圾回收。一旦该委托被垃圾收集,任何回调都将失败。壮观。


1技术上我用于所有测试的项目相同,但我每次都清除代码。
2创意名称,我知道。
3来源:Is it possible to export a C++ member method with C linkage in a DLL?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-03
    相关资源
    最近更新 更多