Большая часть моей разработки на C / C++ включает в себя файлы монолитных модулей и абсолютно никаких классов, поэтому обычно, когда мне нужно создать DLL с доступными функциями, я просто экспортирую их, используя стандартную директиву __declspec(dllexport). Затем обращайтесь к ним либо динамически через LoadLibrary(), либо во время компиляции с помощью файла заголовка и lib.
Как вы это делаете, если хотите экспортировать весь класс (и все его общедоступные методы и свойства)?
Можно ли динамически загружать этот класс во время выполнения, и если да, то как?
Как бы вы сделали это с заголовком и библиотекой для компоновки во время компиляции?





Я использую некоторые макросы, чтобы пометить код для импорта или экспорта
#ifdef ISDLL #define DLL __declspec(dllexport) #endif #ifdef USEDLL #define DLL __declspec(dllimport) #endif
Затем объявите класс в файле заголовка:
class DLL MyClassToExport { ... }
Затем #define ISDLL в библиотеке и USEDLL перед включением файла заголовка в то место, где вы хотите использовать класс.
Не знаю, может понадобиться что-то другое для работы с LoadLibrary.
Когда вы создаете DLL и модуль, который будет использовать DLL, имейте какой-то #define, который вы можете использовать для различения одного и другого, тогда вы можете сделать что-то вроде этого в файле заголовка вашего класса:
#if defined( BUILD_DLL )
#define IMPORT_EXPORT __declspec(dllexport)
#else
#define IMPORT_EXPORT __declspec(dllimport)
#endif
class IMPORT_EXPORT MyClass {
...
};
Обновлено: crashmstr опередил меня!
как бороться с искаженными именами классов и конструкторов (и т. д.), сэр.?
В том-то и дело - вам не нужно иметь дело с искаженными именами. __declspec(dllimport) сообщает компилятору / компоновщику, что вы его импортируете, и он поступает правильно.
Ни этот ответ, ни ответ crashmstr не имеют ничего общего с исходным вопросом. Также вопрос null не был должным образом решен.
@AudioGL О чем ты говоришь? Если вы включаете файл заголовка, который определяет класс с помощью __declspec(dllimport), а затем связываете exe с библиотекой импорта (в результате сборки DLL), полученный исполняемый файл автоматически загружает DLL во время выполнения и импортирует класс, включая все методы на нем. . Искаженные имена и перегруженные конструкторы не являются проблемой, потому что все они обрабатываются компоновщиком и загрузчиком. Это может быть не такой полный ответ, как ответ Джеймса Девлина, но в этом нет ничего плохого, и он действительно отвечает на исходный вопрос.
Фактически, если вы читаете ответ г-на Девлина, он говорит: «Возможно, лучше просто использовать неявное связывание, и в этом случае вы определенно хотите использовать метод препроцессора, показанный выше» (выделено мной).
Извините - я предположил, что Адам мог иметь дело с проблемами несовместимости компилятора. Вы не можете использовать этот метод, если вы пытались экспортировать классы между MSVC и MinGW, например, из-за различных схем изменения имен.
What about late-binding? As in loading it with LoadLibrary() and GetProcAddress() ? I'm used being able to load the library at run time and it would be great if you could do that here.
Итак, есть два способа загрузить DLL. Первый - указать один или несколько символов из DLL (например, имя вашего класса), предоставить соответствующий import .LIB и позволить компоновщику все выяснить.
Второй - явно загрузить DLL через LoadLibrary.
Любой из подходов отлично подходит для экспорта функций уровня C. Вы можете либо позволить компоновщику обработать это, либо вызвать GetProcAddress, как вы отметили.
Но когда дело доходит до экспортированного классы, обычно используется только первый подход, то есть неявная ссылка на DLL. В этом случае DLL загружается во время запуска приложения, и приложение не загружается, если DLL не может быть найдена.
Если вы хотите связать класс, определенный в DLL, и хотите, чтобы эта DLL загружалась динамически, через некоторое время после запуска программы у вас есть два варианта:
Создавайте объекты класса, используя специальную фабричную функцию, которая внутри должна будет использовать (крошечный бит) ассемблер для «подключения» вновь созданных объектов к их соответствующим смещениям. Очевидно, это должно быть сделано во время выполнения ПОСЛЕ загрузки DLL. Хорошее объяснение этого подхода можно найти в здесь.
Используйте DLL с отложенной загрузкой.
Учитывая все обстоятельства ... вероятно, лучше просто использовать неявное связывание, и в этом случае вы определенно захотите использовать метод препроцессора, показанный выше. Фактически, если вы создадите новую DLL в Visual Studio и выберете опцию «экспорт символов», эти макросы будут созданы для вас.
Удачи...
Я не верю, что вы можете использовать delayload с импортированными классами, если классы имеют виртуальные функции, потому что vtables этих классов будут импортированы как символы данных. Импорт символов данных предварительно вычисляется с использованием delayload.
Если вы хотите поместить vtable в экспортируемый класс, вы можете экспортировать функцию, которая возвращает интерфейс и реализовать класс в .dll, а затем поместить ее в файл .def. Возможно, вам придется проделать некоторые уловки с объявлением, но это не должно быть слишком сложно.
Так же, как COM. :)
Недавно я задал себе точно такой же вопрос и резюмировал свои выводы в сообщении в блоге. Вы можете найти это полезным.
Он охватывает экспорт классов C++ из DLL, а также их динамическую загрузку с помощью LoadLibrary и обсуждает некоторые связанные с этим проблемы, такие как управление памятью, изменение имен и соглашения о вызовах.
Добавляем простой рабочий пример для экспорта класса C++ из DLL:
Приведенный ниже пример дает вам только краткий обзор того, как dll и exe могут взаимодействовать друг с другом (не требует пояснений), но для изменения в производственный код нужно добавить больше вещей.
Полный примерный пример разделен на две части.
A. Создание библиотеки .dll (MyDLL.dll)
Б. Создание приложения, использующего библиотеку .dll (приложение).
A. Файл проекта .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. Проект приложения, который загружает и связывает уже созданный файл .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;
}
Спасибо. Без подхода класса интерфейса в этом ответе мне пришлось поместить все в файл .h, чтобы предотвратить ошибки компоновщика.
зачем нужна заводская функция? Если без заводской функции, как мне поступить?
Очень хороший ответ, написанный позже: stackoverflow.com/a/22797419/1995714