C ++:从DLLdynamic加载类

对于我当前的项目,我希望能够从一个DLL(这不总是相同的,甚至可能不存在,当我的应用程序编译)加载一些类。 对于一个给定的类,也可能有多个可选的dll(例如Direct3D9和OpenGL的一个实现),但是任何时候只有一个dll会被加载/使用。

我有一组基类定义接口加上一些基本的方法/成员(即用于引用计数的)我想要加载的类,这些DLL项目然后从创build那里派生类。

//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链接器sorting全部使用编译dll时得到的.lib文件。

我不介意使用工厂方法实例化类,他们中的许多已经通过devise(即一个Sprite类是由主Graphics类创build的,例如Graphics-> CreateSpriteFromTexture(base :: Texture *)

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

所得到的DLL可以通过“import [dllname]”导入到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 ++环境中使用它来解决我的问题吗?

最简单的方法,恕我直言,是有一个简单的C函数返回一个指向其他地方描述的接口。 那么你的应用程序可以调用该接口的所有功能,而不用真正知道它正在使用什么类。

编辑:这是一个简单的例子。

在您的主应用程序代码中,为接口创建一个标题:

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

主应用程序使用上面的接口进行编码,没有任何关于接口实际执行的细节。

 class Actualmodulee: public Imodulee { /* implementation */ }; 

现在,模块–DLL具有该接口的实际实现,并且这些类甚至不必被导出 – 不需要__declspec (dllexport) 。 模块的唯一要求是导出一个函数,这将创建并返回接口的实现:

 __declspec (dllexport) Imodulee* Createmodulee() { // call the constructor of the actual implementation Imodulee * module = new Actualmodulee(); // return the created function return module; } 

注意:错误检查遗漏了 – 你通常要检查,如果新返回正确的指针,你应该保护自己免受可能在Actualmodulee类的构造函数中抛出的异常。

然后,在您的主应用程序中,只需加载模块( LoadLibrary函数)并找到函数CreatemoduleeGetProcAddr函数)即可。 然后,通过界面使用该类。

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

你正在重新发明COM。 你的RefCounted类是IUnknown。 你的抽象类是一个接口。 DLL中的COM服务器有一个名为DllGetClassObject()的入口点,它是一个类工厂。 有很多关于COM的文档可以从微软获得,捅一下看看他们是如何做到的。

[编辑:虽然我正在撰写所有这些,保利斯Maruš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”确保这个名字不被打乱,最终不会像“CreateSprite @ 4”那样。

另外两个笔记:

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

也许你想看看DLL延迟加载( http://www.codeproject.com/KB/DLL/Delay_Loading_Dll.aspx ) – 这会给你你想要什么,而不必为此工作太辛苦