Вызов C ++ неуправляемых функций-членов из C #, когда функции-члены не были экспортированы? - PullRequest
2 голосов
/ 28 августа 2011

Итак, у меня есть неуправляемая 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 (), но есть ли способ получить доступ к функциям-членам возвращенной «структуры» ... т.е. sayHello и release?

Ответы [ 3 ]

4 голосов
/ 28 августа 2011

Хорошо ... дело сложное.Действительно сложно.

Я должен предупредить, что для доступа к этим методам предпочтительнее другой метод.Создайте функцию C для каждого метода или используйте COM.Выберите, какой метод вы предпочитаете.Оба метода требуют некоторой работы на стороне C ++.

Поскольку мы пытаемся сейчас ответить на вопрос, мы пытаемся вызвать метод из класса C ++.

Первая проблема:

Вам нужно украсить все классы или методы классов с помощью __declspec (экспорт).Если символ не экспортируется, вы не сможете получить к нему доступ.Конечно, это не очень хорошо работает с шаблонами.

Вторая проблема:

Методы класса используют соглашение о вызовах __thiscall.Для P / Invoke по умолчанию требуется соглашение __stdcall.

Однако вы можете изменить атрибут CallingConvention на CallingConvention.ThisCall в DllImportAttribute, поэтому проблема решена.

Третья проблема:

Каждый компилятор 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 ++.

ЭтоЭто самая большая головная боль, которую вы можете испытать на этом пути!

Пятая проблема

Сборщик мусора и безопасное удаление объектов ... Вместо IntPtr используйте SafeHandles, оба, если вы собираетесь использовать функции Сиили если вы используете способ C ++.

Вы можете определить SafeHandle, который представляет указатель на ваш объект C ++.Этот SafeHandle должен вызывать деструктор вашего объекта C ++ в переопределенной функции ReleaseHandle.

Заключение

Как я уже говорил, просто использовать функции C или COM-объекты будет проще, более переносимо и менее утомительно.

COM-взаимодействие было построено, чтобы избежать проблем с собственными функциями компилятора C ++.Com - это старая технология, у которой много проблем и странных вещей, но она все еще работает, все еще поддерживается и будет поддерживаться в течение длительного времени.

Если ваш граф объектов в C ++ будет довольно сложным,используйте COM.Инструмент C # tlbimp автоматически генерирует классы C #, которые связываются с COM-объектами на лету, без необходимости делать какие-либо DllImport.

Если вам нужны простые вещи, просто используйте простой C.

1 голос
/ 28 августа 2011

Может быть, есть и другие способы и приемы, но, поскольку ваш класс не является 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;
}
0 голосов
/ 20 апреля 2015

Я тоже застрял с той же проблемой.Когда я гуглю, могу найти два решения.

Solution1: Предоставить все функции-члены в стиле C.

Solution2: Написать управляемую C ++ dll, раскрывающую функциональность нативной C ++ dll, который позже может быть использован в вашей C # DLL.

Прошло три года, этот вопрос был задан.Есть ли лучшее решение, кроме двух приведенных выше?

Извините, если в сообщении есть какие-либо ошибки.

...