C ++:从dll动态加载类

时间:2009-01-10 18:52:32

标签: c++ windows class dll

对于我当前的项目,我希望能够从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链接器排序全部使用我在编译dll时获得的.lib文件。

我不介意使用工厂方法来实例化类,其中许多已经按设计完成(即一个sprite类是由主要的Graphics类创建的,例如Graphics-> CreateSpriteFromTexture(base :: Texture *)

编辑: 当我需要编写一些用于python的c ++ dll时,我使用了一个名为pyCxx的库。生成的dll基本上只导出一个方法,该方法创建了一个“Module”类的实例,然后可以包含工厂方法来创建其他类等。

可以使用“import [dllname]”将结果dll导入到python中。

//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");
    }

这究竟是如何工作的,我可以在纯粹的c ++环境中使用它来解决我的问题吗?

4 个答案:

答案 0 :(得分:20)

最简单的方法是,恕我直言,有一个简单的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函数)并找到函数CreateModuleGetProcAddr函数)。然后,通过界面使用该类。

编辑2:您的RefCount(界面的基类),可以在主应用程序中实现(并从中导出)。然后你的所有模块都需要链接到主应用程序的lib文件(是的!EXE文件可以像DLL文件一样有LIB文件!)这应该就够了。

答案 1 :(得分:16)

您正在重新发明COM。您的RefCounted课程是IUnknown。您的抽象类是一个接口。 DLL中的COM服务器有一个名为DllGetClassObject()的入口点,它是一个类工厂。微软在COM上提供了大量文档,稍微了解一下它们是如何做到的。

答案 2 :(得分:4)

<编辑:编辑:当我写这一切时,PauliusMaruška编辑他的评论基本相同。所以重复道歉。虽然我想你现在有一个备用的:)]

脱离我的头脑,并假设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”确保名称是unmangled并且不会以“CreateSprite @ 4”或类似的东西结束。

另外两个注释:

  1. GetProcAddress如果找不到该函数则返回0,因此您可以使用它来扫描DLL列表(例如,从FindFirstFile和朋友返回),查找支持该接口的DLL,和/或尝试查找多个入口点,每个插件支持多种类型。
  2. 如果主程序要使用delete运算符删除精灵,这需要使用相同的运行时库构建DLL和主程序,以便主程序的删除和DLL的新都使用相同的堆。你可以通过让DLL提供一个DeleteSprite函数在某种程度上解决这个问题,该函数使用DLL的运行时库而不是主程序删除精灵,但是你仍然需要关心程序的其余部分,所以你可能只是想强制DLL编写器使用与程序相同的运行时库。 (我不会说你这样做会很好,但这并不罕见。例如,3D Studio MAX需要这个。)

答案 3 :(得分:2)

也许你想看看DLL延迟加载(http://www.codeproject.com/KB/DLL/Delay_Loading_Dll.aspx) - 这将为你提供你想要的东西,而不必为它做太多努力