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

С++: динамическая загрузка классов из dlls

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

У меня есть набор базовых классов, которые определяют интерфейс плюс некоторые базовые методы/члены (т.е. те, которые используются для подсчета refrence) классов, которые я хочу загрузить, из которых затем создаются 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 могли загружать и использовать эти классы, поскольку только ive использовала только DLL, где была только одна dll, и я мог бы иметь компоновщик Visual Studio все это с использованием .lib файла, который я получаю при компиляции dll.

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

EDIT: Когда мне нужно было написать некоторые dll С++ для использования в python, я использовал библиотеку под названием pyCxx. В результате dll в основном экспортировал только один метод, который создал экземпляр класса "Module", который затем мог содержать методы factory для создания других классов и т.д.

Полученная 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");
    }

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

4b9b3361

Ответ 1

Самый простой способ сделать это, 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;
}

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

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

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

Ответ 2

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

Ответ 3

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

Откиньте верхнюю часть головы и предположим, что Visual С++...

Вам нужно использовать 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" или что-то вроде.

Две другие заметки:

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

Ответ 4

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