C ++: динамическая загрузка классов из DLL - PullRequest
22 голосов
/ 10 января 2009

Для моего текущего проекта я хочу иметь возможность загружать некоторые классы из библиотеки DLL (которая не всегда одинакова и может даже не существовать, когда мое приложение компилируется). Также может быть несколько альтернативных dll для данного класса (например, реализация для Direct3D9 и одна для OpenGL), но только один из dll будет загружен / использован одновременно.

У меня есть набор базовых классов, которые определяют интерфейс, а также некоторые базовые методы / члены (то есть, те, которые используются для подсчета ссылок) классов, которые я хочу загрузить, и которые затем создаются проектами dll при создании там классов.

//in namespace base
class Sprite : public RefCounted//void AddRef(), void Release() and unsigned refCnt
{
public:
    virtual base::Texture *GetTexture()=0;
    virtual unsigned GetWidth()=0;
    virtual unsigned GetHeight()=0;
    virtual float GetCentreX()=0;
    virtual float GetCentreY()=0;
    virtual void SetCentre(float x, float y)=0;

    virtual void Draw(float x, float y)=0;
    virtual void Draw(float x, float y, float angle)=0;
    virtual void Draw(float x, float y, float scaleX, flota scaleY)=0;
    virtual void Draw(float x, float y, float scaleX, flota scaleY, float angle)=0;
};

Дело в том, что я не уверен, как все это сделать, чтобы исполняемый файл и другие dll могли загружать и использовать эти классы, так как я когда-либо использовал dll только там, где был только один dll, и я мог иметь компоновщик Visual Studio все это с помощью .lib файла, который я получаю при компиляции DLL.

Я не возражаю против использования фабричных методов для создания экземпляров классов, многие из них уже проектируют (т. Е. Класс спрайтов создается основным классом Graphics, например Graphics-> CreateSpriteFromTexture (base :: Texture *)

EDIT: Когда мне нужно было написать несколько библиотек c ++ для использования в python, я использовал библиотеку pyCxx. Результирующая dll в основном экспортировала только один метод, который создал экземпляр класса «Модуль», который затем мог бы содержать фабричные методы для создания других классов и т. Д.

Получившаяся dll может быть импортирована в python просто с помощью "import [dllname]".

//dll compiled as cpputill.pyd
extern "C" void initcpputill()//only exported method
{
    static CppUtill* cpputill = new CppUtill;
}

class CppUtill : public Py::ExtensionModule<CppUtill>
{
public:
    CppUtill()
    : Py::ExtensionModule<CppUtill>("cpputill")
    {
        ExampleClass::init_type();

        add_varargs_method("ExampleClass",&CppUtill::ExampleClassFactory, "ExampleClass(), create instance of ExampleClass");
        add_varargs_method("HelloWorld",  &CppUtill::HelloWorld,  "HelloWorld(), print Hello World to console");

        initialize("C Plus Plus module");
    }
...
class ExampleClass
...
    static void init_type()
    {
        behaviors().name("ExampleClass");
        behaviors().doc ("example class");
        behaviors().supportGetattr();
        add_varargs_method("Random", &ExampleClass::Random, "Random(), get float in range 0<=x<1");
    }

Как именно это работает, и могу ли я использовать его в чисто с ++ среде для решения моей проблемы здесь?

Ответы [ 4 ]

21 голосов
/ 10 января 2009

Самый простой способ сделать это, IMHO, это иметь простую функцию C, которая возвращает указатель на интерфейс, описанный в другом месте. Тогда ваше приложение может вызывать все функции этого интерфейса, фактически не зная, какой класс оно использует.

Редактировать: вот простой пример.

В вашем основном коде приложения вы создаете заголовок для интерфейса:

class IModule
{
public:
    virtual ~IModule(); // <= important!
    virtual void doStuff() = 0;
};

Главное приложение написано для использования интерфейса выше, без каких-либо подробностей о фактической реализации интерфейса.

class ActualModule: public IModule
{
/* implementation */
};

Теперь, модули - библиотеки DLL имеют фактические реализации этого интерфейса, и эти классы даже не нужно экспортировать - __declspec (dllexport) не требуется. Единственное требование к модулям - экспортировать одну функцию, которая создаст и вернет реализацию интерфейса:

__declspec (dllexport) IModule* CreateModule()
{
    // call the constructor of the actual implementation
    IModule * module = new ActualModule();
    // return the created function
    return module;
}

примечание: проверка ошибок пропущена - вам, как правило, нужно проверять, вернул ли new правильный указатель, и вам следует защитить себя от исключений, которые могут быть сгенерированы в конструкторе класса ActualModule.

Затем в главном приложении достаточно просто загрузить модуль (функция LoadLibrary) и найти функцию CreateModule (функция GetProcAddr). Затем вы используете класс через интерфейс.

Редактировать 2: ваш RefCount (базовый класс интерфейса) может быть реализован (и экспортирован из) в основное приложение. Тогда весь ваш модуль должен будет ссылаться на файл lib основного приложения (да! Файлы EXE могут иметь файлы LIB точно так же, как файлы DLL!) И этого должно быть достаточно.

16 голосов
/ 10 января 2009

Вы заново изобретаете COM. Ваш класс RefCounts - IUnknown. Ваш абстрактный класс - это интерфейс. COM-сервер в DLL имеет точку входа с именем DllGetClassObject (), это фабрика классов. Microsoft предлагает много документации по COM, немного поглядите, как они это сделали.

4 голосов
/ 11 января 2009

[Редактировать: пока я все это сочинял, Паулюс Марушка отредактировал свой комментарий, сказав в основном то же самое. Так что извиняюсь за любое дублирование. Хотя, я полагаю, у вас теперь есть запасной:)]

С макушки головы и при условии, что Visual C ++ ...

Вам необходимо использовать LoadLibrary для динамической загрузки DLL, а затем использовать GetProcAddress, чтобы извлечь из нее адрес функции, которая создаст фактический производный класс, который реализует код DLL. Как именно вы решите сделать это, зависит только от вас (библиотеки DLL нуждаются в поиске, способ выставления их функциональности требует указания и т. Д.), Поэтому пока давайте предположим, что плагины предоставляют только новые реализации Sprite.

Для этого выберите сигнатуру функции в DLL, которую основная программа вызовет для создания одного из этих новых спрайтов. Это выглядит подходящим:

typedef Sprite *(*CreateSpriteFn)();

Затем из основной программы вам нужно загрузить DLL (опять же, как вы обнаружите, что эта DLL на ваше усмотрение) и получить из нее функцию создания спрайта. Я решил, что функция создания спрайта будет называться «CreateSprite»:

HMODULE hDLL=LoadLibrary(pDLLName);
CreateSpriteFn pfnCreateSprite=CreateSpriteFn(GetProcAddress(hDLL,"CreateSprite"));

Затем, чтобы создать одно из них, просто вызовите функцию:

Sprite *pSprite=(*pfnCreateSprite)();

Как только вы закончили работу с DLL и не осталось объектов, созданных ею, вы затем используете FreeLibrary, чтобы избавиться от нее:

FreeLibrary(hDLL);

Чтобы создать DLL, поддерживающую этот интерфейс, напишите код для производного класса и т. Д., А затем где-нибудь в коде DLL предоставьте функцию CreateSprite, необходимую для вызывающей программы, используя соответствующую сигнатуру:

__declspec(dllexport)
extern "C"
Sprite *CreateSprite()
{
    return new MyNewSprite;
}

Функция dllexport означает, что вы можете использовать GetProcAddress, чтобы подобрать эту функцию по имени, а extern "C" гарантирует, что имя не будет исправлено и не будет заканчиваться как "CreateSprite @ 4" или что-то вроде. *

Два других примечания:

  1. GetProcAddress возвращает 0, если не удалось найти функцию, поэтому вы можете использовать ее для сканирования списка библиотек DLL (например, возвращенных из FindFirstFile и друзей), поиска библиотек DLL, которые поддерживают интерфейс, и / или попытки найти несколько точек входа и поддержка нескольких типов на плагин.
  2. если основная программа собирается удалить спрайт с помощью оператора удаления, для этого требуется, чтобы как DLL, так и основная программа создавались с использованием одной и той же библиотеки времени выполнения, чтобы при удалении основной программы и новой DLL использовалась одна и та же куча , Вы можете обойти это до некоторой степени, если в DLL есть функция DeleteSprite, которая удаляет спрайт с помощью библиотеки времени выполнения DLL, а не основной программы, но вам все равно придется позаботиться об остальной части вашей программы, так что вы можете просто хотите заставить авторов DLL использовать ту же библиотеку времени выполнения, что и ваша программа. (Я бы не сказал, что этим вы будете в хорошей компании, но это не редкость. Например, 3D Studio MAX требует этого.)
2 голосов
/ 10 января 2009

Возможно, вы хотите посмотреть на DLL Delay-Loading (http://www.codeproject.com/KB/DLL/Delay_Loading_Dll.aspx) - это даст вам то, что вы хотите, без необходимости слишком усердно работать для этого

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...