Подтвердить что ты не робот

Как сделать DllExport классом С++ для использования в приложении С#

Я создал проект С++ Dll, который содержит класс "myCppClass" и попытался экспортировать Dll с помощью следующего кода, как описано в: http://msdn.microsoft.com/en-us/library/a90k134d(v=vs.80).aspx

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

Я опустил "public CObject", поскольку для этого требуется afx.h и подразумевает, что это MFC Dll. Я не уверен, что это хорошо или нет, но оно отличается от настроек по умолчанию проекта DLL.

Из приведенной выше документации я убежден, что все "публичные функции и переменные-члены" доступны для импорта. Как это сделать на С#? Может просто создать экземпляр класса?

Изменить: я только понял, что Название сообщения может вводить в заблуждение. Акцент должен делаться на DllImport-ing из С# и гарантировать, что я правильно выполнил документацию на С++

4b9b3361

Ответ 1

С# не может напрямую импортировать классы С++ (которые фактически управляются с помощью интерфейсов C).

Ваши параметры выставляют класс через COM, создавая управляемую оболочку с использованием С++/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)
}

Оболочка типа С++/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;
    }
};

Ответ 2

Насколько я знаю, С# может взаимодействовать только с интерфейсами COM. К счастью, ему не нужно быть полномасштабным COM-объектом с реестром, это может быть любой простой старый класс С++, реализующий IUnknown.

Так что сделайте что-нибудь подобное в С++:

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

И на стороне С# вы делаете что-то вроде этого:

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

Вы можете делать почти все, что хотите, до тех пор, пока связь между С++ и С# проходит через виртуальный интерфейс.

Ответ 3

Вы не можете создать экземпляр класса С++ через pinvoke из С#. Это сложная деталь реализации, только компилятор С++ знает, сколько памяти нужно выделить, и когда и как правильно вызвать конструктор и деструктор. Размер объекта, безусловно, самый твердый орех для взлома, нет никакого способа сделать это надежным.

Если вы не можете сгладить класс С++ в статические методы, вам нужно написать управляемую оболочку. Это сделано с языком С++/CLI, вы должны написать "класс ref", который имеет неуправляемый объект класса, сохраненный как указатель, созданный в конструкторе и удаленный в деструкторе и финализаторе.

Ответ 4

Собственно, вы можете напрямую ссылаться на искаженные имена, используя свойство EntryPoint DllImport. Подробнее см. этот ответ.

Ответ 5

С# и С++ не совместимы с ABI, такими как С++ и Delphi, поэтому вы не можете экспортировать члены виртуального класса (методы) и объявлять их чисто виртуальными на вызывающей стороне, вызывать их, потому что С# не может обрабатывать vtbl объектов С++. Я бы предложил вам объединить ваши классы С++ с помощью COM, так что у вас есть еще один положительный побочный эффект, что другие совместимые с COM языки могут также использовать ваши классы.