【问题标题】:Calling C++ unmanaged member functions from C# when member functions were not exported?未导出成员函数时从 C# 调用 C++ 非托管成员函数?
【发布时间】:2011-08-28 13:33:46
【问题描述】:

所以我有一个非托管 DLL,它只导出一个 C 风格的工厂方法,该方法返回一个类的新实例(这里简化为看起来很简单)。

你好.h

#if defined(HWLIBRARY_EXPORT) // inside DLL
#   define HWAPI   __declspec(dllexport)
#else // outside DLL
#   define HWAPI   __declspec(dllimport)
#endif

struct HelloWorld{
   public:
    virtual void sayHello() = 0;
    virtual void release() = 0;
};
extern "C" HWAPI HelloWorld* GetHW();

你好.cpp

  #include "hello.h"

struct HelloWorldImpl : HelloWorld
{
    void sayHello(){
        int triv;
        std::cout<<"Hello World!";
        std::cin>>triv;
    };
    void release(){
        this->HelloWorldImpl::~HelloWorldImpl();
    };
};
HelloWorld* GetHW(){
    HelloWorld* ptr = new HelloWorldImpl();
    return ptr;
};

现在,我可以使用 dllimport 来访问 GetHW(),但是有没有办法访问返回的“结构”的成员函数...即 sayHello 和 release?

【问题讨论】:

  • 调用实例函数需要 ThisCall 调用约定,而不是由 pinvoke 编组器实现。你的 release() 函数不能工作,它不会释放内存。这样做的唯一明智的方法是用 C++/CLI 语言编写一个 ref 类包装器。
  • +1 用于指出内存泄漏和引用类包装器。

标签: c# visual-studio-2008 visual-c++


【解决方案1】:

好吧……事情很复杂。真的很复杂。

我应该警告说,最好使用另一种方法来访问这些方法。 为每个方法创建一个 C 函数或使用 COM。 选择您喜欢的方法。 这两种方法都需要在 C++ 方面进行一些工作。

因为我们现在试图回答这个问题,所以我们尝试从 C++ 类中调用一个方法。

第一个问题:

你需要用 __declspec(export) 来装饰所有的类或类方法。 如果未导出符号,则无法访问该符号。 当然,这不适用于模板。

第二个问题:

类方法使用调用约定__thiscall。 默认 P/Invoke 需要 __stdcall 约定。

但是你可以在 DllImportAttribute 中将 CallingConvention 属性更改为 CallingConvention.ThisCall,这样问题就解决了。

第三题:

每个 C++ 编译器都有自己的方式来生成导出的 C++ 成员名称!

在这篇文章中,我首先假设 DLL 是使用 Microsoft Visual C++ 编译的! 不知道其他编译器会发生什么。

DllImport 属性需要方法的确切名称。 由于它是由 C++ 编译器生成的,因此如果不查看 DLL 导出表(这是由于虚拟、重载、命名空间、类名等原因),绝对没有简单的方法可以恢复名称。

我知道获取方法名称的唯一方法是使用 dll 转储工具,例如,我经常使用“dumpbin.exe /EXPORTS file.dll”。 这将打印出您的 dll 方法。 您也可以使用 rtti 打印方法名称,这可以帮助您使用代码映射 dll 导出。

现在我们可以编写我们的 dllimport。 例如。

[DllImport(
    "MyDll.dll",
    EntryPoint="?MyFunction@CMyClass@@QAEXH@Z",
    CallingConvention=CallingConvention.ThisCall)]
public static extern void MyFunction(IntPtr myClassObject, int parameter);

EntryPoint 属性是导出方法的确切名称。

好的,我们已经完成了,但是让我们分析一下这样做的问题:

第四个问题

微软没有官方文档解释如何准确地生成用于 c++ 导出的方法名称,因此如果您修改 C++ 源代码,导出的名称可能会发生变化。

这是你走这条路时最头疼的问题!

第五题

垃圾收集器和安全对象处理... 如果要使用 C 函数或使用 C++ 方式,请使用 SafeHandles,而不是 IntPtr。

您可以定义一个代表指向您的 C++ 对象的指针的 SafeHandle。 此 SafeHandle 必须在重写的 ReleaseHandle 函数中调用 C++ 对象的析构函数。

结论

正如我之前所说,只使用 C 函数或 COM 对象会更容易、更便携且不那么乏味。

COM 互操作旨在避免本机 C++ 编译器功能出现问题。 com 是一项老技术,有很多问题和奇怪的东西,但仍然有效,仍然支持并将长期支持。

如果 C++ 中的对象图非常复杂,请使用 COM。 C# tlbimp 工具自动生成绑定到 COM 对象的 C# 类,无需执行任何 DllImport。

如果您需要简单的东西,只需使用普通 C。

【讨论】:

    【解决方案2】:

    也许还有其他一些方法和技巧,但是由于您的类不是 POD,因此对我来说安全且经过尝试的方法是将每个成员函数导出为带有附加指针参数的非成员函数。

    extern "C" HWAPI HelloWorld* GetHW();
    extern "C" HWAPI void SayHello(HelloWorld* p); //{p->SayHello();}
    extern "C" HWAPI void Releasr(HelloWorld* p); //{p->Release();}
    

    在 C# 中,您导入所有这些,然后创建一个包装类来执行以下操作:

    class HelloWorld
    {
       public HelloWorld() {ptr = Imports.GetHW();}
       public void SayHello {Imports.SayHello(ptr); }
       public ~HelloWorld() {Imports.Release(ptr);}
       private IntPtr ptr;
    }
    

    【讨论】:

      【解决方案3】:

      我也遇到了同样的问题。 当我用谷歌搜索时,能够找到两个解决方案。

      解决方案1: 以 C 风格公开所有成员函数。

      解决方案2: 编写一个托管 C++ dll,公开原生 C++ dll 的功能,以后可以在您的 C# dll 中使用。

      这个问题被问了三年。 除了以上两种方案还有什么更好的方案吗?

      如果帖子有任何错误,请见谅。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-07-29
        • 1970-01-01
        • 1970-01-01
        • 2013-02-15
        • 1970-01-01
        • 2016-08-17
        • 1970-01-01
        相关资源
        最近更新 更多