在未导出成员函数时,从C#调用C ++非托管成员函数?

时间:2011-08-28 13:33:46

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

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

hello.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();

hello.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(),但有没有办法访问返回的'struct'的成员函数...即sayHello和release?

3 个答案:

答案 0 :(得分:4)

好的......事情很复杂。真的很复杂。

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

由于我们现在试图回答这个问题,我们尝试从C ++类调用一个方法。

第一个问题:

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

第二个问题:

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

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

第三个问题:

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

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

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

我知道获取方法名称的唯一方法是使用dll dump工具,例如,我经常使用“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属性是导出方法的确切名称。

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

第四个问题

没有来自microsoft的官方文档解释了如何生成c ++导出的确切方法名称,因此如果修改C ++源代码,导出的名称可能会发生变化。

这是你走这条路的最大麻烦!

第五个问题

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

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

结论

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

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

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

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

答案 1 :(得分:1)

也许还有其他一些方法和技巧,但由于你的类不是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;
}

答案 2 :(得分:0)

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

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

溶液2: 编写一个托管的C ++ DLL,展示本机C ++ DLL的功能,以后可以在C#dll中使用。

问了三年这个问题。 除上述两种解决方案外还有哪些更好的解决方案?

对不起,如果帖子有任何错误。