c# - 将参数中带有回调函数的 C/C++ 函数编码到 C#

标签 c# c++ callback marshalling

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

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);
}

但是我得到了一个异常(exception),

SetProgressHandlerCallBack

at (wrapper managed-to-native) ModellInstantiator:SetProgressHandlerCallBack (ModellInstantiator/HandlerCallBack)

我不知道我错过了什么。

最佳答案

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

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

这有 2 个问题,这两个问题都可以用一个简单的最小完整可验证示例来解释,我创建了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#项目的build目录下,运行。单击 button1,调用回调,并显示消息框。单击按钮 2,程序崩溃并出现以下异常:

Unable to find an entry point named 'CallMeBack' in DLL 'CppDllTestProj.dll'. at ScratchApp.Form1.CallMeBack(HandlerCallbackDelegate callback) at ScratchApp.Form1.button2_Click(Object sender, EventArgs e) in Form1.cs:line 49

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

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

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

此问题的根源是 C++ 类和类成员忽略了 extern "c" 存储类说明符3

修复上述问题后,您将遇到的另一个问题是这段代码:

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

您会注意到,当控制从对 SetProgressHandlerCallBack 的调用返回时,cb 立即可用于垃圾回收。一旦该委托(delegate)被垃圾回收,任何回调都将失败。壮观。


<子> 1从技术上讲,我使用同一个项目进行所有测试,但我每次都清除代码。
2创意名称,我知道。
3来源:Is it possible to export a C++ member method with C linkage in a DLL?

关于c# - 将参数中带有回调函数的 C/C++ 函数编码到 C#,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37756177/

相关文章:

c++ - 您能告诉我为什么出现此错误吗?它涉及到函数指针

java - 回调抛出 NullPointerException( fragment 超出适配器)

javascript - 应该在排序函数之后的回调中设置状态吗?

c# - 通过 C#(VSTO)/VBA 访问 Excel 2010 公式编辑器

c# - 如何将值的数据行数组传递给javascript

c++ - 实际上,自定义的 Double 类会比内置的 double 慢得多吗?

jQuery .animate 不透明度回调函数

c# - 从 T4 模板中的 CodeProperty 检索属性类型

c# - 使用 XMLTextReader 读取节点值

c++ - 如何使用 shell 脚本运行一个进程两次?