如何将C++类导出为Dll以供C#应用程序使用

15

我创建了一个C++ Dll项目,其中包含一个名为"myCppClass"的类,并尝试使用以下代码导出Dll,如下面提到的http://msdn.microsoft.com/en-us/library/a90k134d(v=vs.80).aspx所述。

class __declspec(dllexport) CExampleExport : //public CObject
{ ... class definition ... };

由于需要包含afx.h并暗示这是一个MFC Dll,因此我省略了“public CObject”。我不确定这是好事还是坏事,但它与DLL项目的默认设置不同。

从上面链接的文档中,我得出结论,所有“公共函数和成员变量”都可以导入。如何在C#中实现这一点?只需实例化该类即可吗?

编辑:我刚意识到帖子的标题可能会产生歧义。重点应该放在从C#中进行DllImport,并确保我在C++中遵循了文档的要求。


1
你需要将DLL COM可见。我将其作为评论发布,因为我没有时间给出更详细的解释。 - Ed S.
5个回答

18

C#无法直接导入C++类(它们实际上是名称混淆的C接口)。

您的选择是通过COM公开该类、使用C++/CLI创建托管包装器或公开一个C样式的接口。我建议使用托管包装器,因为这是最容易的,并将提供最佳的类型安全性。

C样式的接口可能类似于以下代码(警告:未经测试):

extern "C" __declspec(dllexport)
void* CExampleExport_New(int param1, double param2)
{
    return new CExampleExport(param1, param2);
}

extern "C" __declspec(dllexport)
int CExampleExport_ReadValue(void* this, int param)
{
    return ((CExampleExport*)this)->ReadValue(param)
}

一个类似于C++/CLI的包装器应该长这样(注意:未经测试的代码):

ref class ExampleExport
{
private:
    CExampleExport* impl;
public:
    ExampleExport(int param1, double param2)
    {
        impl = new CExampleExport(param1, param2);
    }

    int ReadValue(int param)
    {
        return impl->ReadValue(param);
    }

    ~ExampleExport()
    {
        delete impl;
    }
};

1
你不能从 void* 进行 dynamic_cast。最好使用真实的指针类型,C# 在 p/invoke 签名中会使用 IntPtr - Ben Voigt
@Ben 你说得对。我在校对时将其从(T*)转换为了原始指针类型,现在已经改回来了。虽然也可以使用真实的指针类型,但我更愿意强调这种方法的不安全性。 - Zooba
如果您喜欢C++模板风格的转换,那么static_cast是您要使用的。 - Ben Voigt
只有当它具有某些有用的行为时才会使用。 dynamic_cast 是我唯一使用的一个(除非我试图树立一个好的例子……这并不经常发生)。 - Zooba

12
据我所知,C#只能与COM接口进行交互。幸运的是,它不需要成为一个完整的带有注册表的COM对象,可以是任何实现IUnknown接口的普通C++类。
因此,在C++中可以这样做:
#include <Windows.h>

// Generate with from VisualStudio Tools/Create Guid menu
static const GUID IID_MyInterface = 
{ 0xefbf7d84, 0x3efe, 0x41e0, { 0x95, 0x2e, 0x68, 0xa4, 0x4a, 0x3e, 0x72, 0xca } };

struct MyInterface: public IUnknown
{
    // add your own functions here
    // they should be virtual and __stdcall
    STDMETHOD_(double, GetValue)() = 0;
    STDMETHOD(ThrowError)() = 0;
};

class MyClass: public MyInterface
{
    volatile long refcount_;

public:
    MyClass(): refcount_(1) { }

    STDMETHODIMP QueryInterface(REFIID guid, void **pObj) {
        if(pObj == NULL) {
            return E_POINTER;
        } else if(guid == IID_IUnknown) {
            *pObj = this;
            AddRef();
            return S_OK;
        } else if(guid == IID_MyInterface) {
            *pObj = this;
            AddRef();
            return S_OK;
        } else {
            // always set [out] parameter
            *pObj = NULL;
            return E_NOINTERFACE;
        }
    }

    STDMETHODIMP_(ULONG) AddRef() {
        return InterlockedIncrement(&refcount_);
    }

    STDMETHODIMP_(ULONG) Release() {
        ULONG result = InterlockedDecrement(&refcount_);
        if(result == 0) delete this;
        return result;
    }

    STDMETHODIMP_(DOUBLE) GetValue() {
        return 42.0;
    }

    STDMETHODIMP ThrowError() {
        return E_FAIL;
    }
};

extern "C" __declspec(dllexport) LPUNKNOWN WINAPI CreateInstance()
{
    return new MyClass();
}

在C#端,您可以这样做:

[ComImport]
[Guid("EFBF7D84-3EFE-41E0-952E-68A44A3E72CA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface MyInterface
{
    [PreserveSig] double GetValue();
    void ThrowError();
}

class Program
{
    [DllImport("mylib.dll")]
    static extern MyInterface CreateInstance();

    static void Main(string[] args)
    {
        MyInterface iface = CreateInstance();
        Console.WriteLine(iface.GetValue());
        try { iface.ThrowError(); }
        catch(Exception ex) { Console.WriteLine(ex); }
        Console.ReadKey(true);
    }
}

只要C++和C#之间的通信通过虚拟接口进行,你就可以以任何想要的方式进行操作。


5
您无法通过C#的pinvoke创建C++类实例。这是一个棘手的实现细节,只有C++编译器知道需要分配多少内存以及何时以及如何正确调用构造函数和析构函数。对象大小是最难解决的问题,没有任何方法可以使其可靠。
如果您无法将C++类压缩为静态方法,则需要编写托管包装器。这是使用C++ / CLI语言完成的,您需要编写一个“ref class”,其中包含作为指针存储的未管理类对象,在构造函数中创建并在析构函数和终结器中删除。

0
实际上,您可以直接引用名称混淆后的名称,使用DllImport属性的EntryPoint属性。有关更多详细信息,请参见此答案

0

C#和C++不像C++和Delphi那样ABI兼容,因此您不能导出虚拟类成员(方法)并在调用方纯粹地声明它们为虚拟的并调用它们,因为C#无法处理C++对象的vtbl。 我建议您通过COM包装您的C++类,这样您还有另一个积极的副作用,即其他兼容COM的语言也可以使用您的类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接