Экспорт класса C++ из DLL
Большая часть моей разработки на C/C++ включает монолитные файлы модулей и абсолютно никаких классов, поэтому обычно, когда мне нужно создать DLL с доступными функциями, я просто экспортирую их, используя стандартные __declspec(dllexport)
директивы. Затем доступ к ним либо динамически через LoadLibrary()
или во время компиляции с заголовком и файлом lib.
Как это сделать, если вы хотите экспортировать весь класс (и все его публичные методы и свойства)?
Можно ли динамически загружать этот класс во время выполнения, и если да, то как?
Как бы вы сделали это с заголовком и библиотекой lib для компиляции во время компиляции?
6 ответов
Как насчет позднего связывания? Как при загрузке его с LoadLibrary() и GetProcAddress()? Я привык иметь возможность загружать библиотеку во время выполнения, и было бы здорово, если бы вы могли сделать это здесь.
Таким образом, есть два способа загрузки DLL. Первый - ссылаться на один или несколько символов из DLL (например, имя вашего класса), предоставлять соответствующий импорт.LIB и позволить компоновщику разобраться во всем.
Второе - явно загрузить DLL через LoadLibrary.
Любой подход хорошо работает для экспорта функций уровня C. Вы можете позволить компоновщику обработать его или вызвать GetProcAddress, как вы заметили.
Но когда дело доходит до экспортируемых классов, обычно используется только первый подход, то есть неявная ссылка на DLL. В этом случае DLL загружается во время запуска приложения, и приложение не загружается, если DLL не может быть найдена.
Если вы хотите создать ссылку на класс, определенный в DLL, и хотите, чтобы эта DLL загружалась динамически, через некоторое время после запуска программы у вас есть два варианта:
Создайте объекты класса, используя специальную фабричную функцию, которая внутренне должна будет использовать (немного) ассемблер, чтобы "подключить" вновь созданные объекты к их соответствующим смещениям. Это должно быть сделано во время выполнения ПОСЛЕ того, как DLL была загружена, очевидно. Хорошее объяснение этого подхода можно найти здесь.
Используйте DLL с задержкой загрузки.
Учитывая все обстоятельства... вероятно, лучше просто использовать неявное связывание, и в этом случае вы определенно захотите использовать технику препроцессора, показанную выше. Фактически, если вы создадите новую DLL в Visual Studio и выберете опцию "экспортировать символы", эти макросы будут созданы для вас.
Удачи...
Когда вы создаете DLL и модуль, который будет использовать DLL, у вас есть какой-то #define, который вы можете использовать для различения одного и другого, тогда вы можете сделать что-то подобное в заголовочном файле вашего класса:
#if defined( BUILD_DLL )
#define IMPORT_EXPORT __declspec(dllexport)
#else
#define IMPORT_EXPORT __declspec(dllimport)
#endif
class IMPORT_EXPORT MyClass {
...
};
Редактировать: crashmstr бить меня к этому!
Добавляем простой рабочий пример для экспорта класса C++ из DLL:
Приведенный ниже пример дает вам только краткий обзор того, как dll и exe могут взаимодействовать друг с другом (не требует пояснений), но для добавления в рабочий код нужно добавить больше вещей.
Пример полного примера разделен на две части
А. Создание библиотеки.dll (MyDLL.dll)
Б. Создание приложения, которое использует библиотеку.dll (Приложение).
А. Файл проекта.dll (MyDLL.dll):
1. dllHeader.h
#ifdef MYDLL_EXPORTS
#define DLLCALL __declspec(dllexport) /* Should be enabled before compiling
.dll project for creating .dll*/
#else
#define DLLCALL __declspec(dllimport) /* Should be enabled in Application side
for using already created .dll*/
#endif
// Interface Class
class ImyMath {
public:
virtual ~ImyMath() {;}
virtual int Add(int a, int b) = 0;
virtual int Subtract(int a, int b) = 0;
};
// Concrete Class
class MyMath: public ImyMath {
public:
MyMath() {}
int Add(int a, int b);
int Subtract(int a, int b);
int a,b;
};
// Factory function that will return the new object instance. (Only function
// should be declared with DLLCALL)
extern "C" /*Important for avoiding Name decoration*/
{
DLLCALL ImyMath* _cdecl CreateMathObject();
};
// Function Pointer Declaration of CreateMathObject() [Entry Point Function]
typedef ImyMath* (*CREATE_MATH) ();
2. dllSrc.cpp
#include "dllHeader.h"
// Create Object
DLLCALL ImyMath* _cdecl CreateMathObject() {
return new MyMath();
}
int MyMath::Add(int a, int b) {
return a+b;
}
int MyMath::Subtract(int a, int b) {
return a-b;
}
B. Application Project, который загружает и связывает уже созданный DLL-файл:
#include <iostream>
#include <windows.h>
#include "dllHeader.h"
int main()
{
HINSTANCE hDLL = LoadLibrary(L"MyDLL.dll"); // L".\Debug\MyDLL.dll"
if (hDLL == NULL) {
std::cout << "Failed to load library.\n";
}
else {
CREATE_MATH pEntryFunction = (CREATE_MATH)GetProcAddress(hDLL,"CreateMathObject");
ImyMath* pMath = pEntryFunction();
if (pMath) {
std::cout << "10+10=" << pMath->Add(10, 10) << std::endl;
std::cout << "50-10=" << pMath->Subtract(50, 10) << std::endl;
}
FreeLibrary(hDLL);
}
std::cin.get();
return 0;
}
Я использую некоторые макросы, чтобы пометить код для импорта или экспорта
#ifdef ISDLL #define DLL __declspec(dllexport) #endif #ifdef USEDLL #define DLL __declspec(dllimport) #endif
Затем объявите класс в заголовочном файле:
Класс DLL MyClassToExport { ... }
затем #define ISDLL
в библиотеке и USEDLL
перед включением файла заголовка в том месте, где вы хотите использовать класс.
Я не знаю, нужно ли вам делать что-то по-другому для работы с LoadLibrary
Недавно я задал себе точно такой же вопрос и резюмировал свои выводы в блоге. Вы можете найти это полезным.
Он охватывает экспорт классов C++ из DLL, а также их динамическую загрузку с LoadLibrary
и обсуждает некоторые вопросы, связанные с этим, такие как управление памятью, распределение имен и соглашения о вызовах.
Если вы хотите поместить vtable в экспортируемый вами класс, вы можете экспортировать функцию, которая возвращает интерфейс и реализовать класс в.dll, а затем поместить ее в файл.def. Возможно, вам придется сделать некоторую хитрость объявления, но это не должно быть слишком сложно.
Так же, как COM.:)