动态加载动态库在很多时候的用户体验都比较好,可以检查发现缺失的文件,可以让可选的模块缺失而继续工作。但是动态加载涉及很多函数定义,函数寻址。写来写去非常繁琐且没有技术含量,遇到C++类导出基本没辙。这里我介绍我使用的一种方便使用的动态加载机制。

  简单的来说就是将欲导出模块封装成类,定义一个纯虚函数类,模块的实现部分做成纯虚函数类的单例。模块仅导出两个函数,一个是创建模块实例,返回虚基类指针;一个是用虚基类指针释放模块。在这种情况下,动态加载模块仍然要处理两个函数,已经好非常多了。 具体的做法如下:

macro.h

//从RakNet库抄来的单例定义
#define _STATIC_FACTORY_DECLARATIONS(x) static x* GetInstance(void); \
    static void DestroyInstance( x *i);
#define _STATIC_FACTORY_DEFINITIONS(x,y) x* x::GetInstance(void) {return new y;} \
    void x::DestroyInstance( x *i) {delete ( y* ) i;}

INetEngine.h

//导出导入的定义
#if !defined(XNETENGINE_STATICLIB)
#  if defined(XNETENGINE_LIBRARY)
#    define XNETENGINE_SHARED Q_DECL_EXPORT
#  else
#    define XNETENGINE_SHARED Q_DECL_IMPORT
#  endif
#else
#  define XNETENGINE_SHARED
#endif

//虚接口定义
class INetEngine
{
   public:
       _STATIC_FACTORY_DECLARATIONS(INetEngine)
       virtual NetID Init(ISinkForNetEngine* iNet)=0;
       //...
       virtual void ManualFree(void* toFree)=0;
};

//正式地导出两个函数
extern "C"
{
    XNETENGINE_SHARED INetEngine* CreateNetEngine();
    XNETENGINE_SHARED void DestroyNetEngine(INetEngine* instance);
}

实现模块功能 .h

class XNetEngine: public INetEngine
{
public:
    XNetEngine(void);
    ~XNetEngine(void);
    NetID Init(ISinkForNetEngine* iNet);
    //...
    void ManualFree(void* toFree);
private:
    //...
}

实现模块功能 .cpp

_STATIC_FACTORY_DEFINITIONS(INetEngine,XNetEngine)
XNETENGINE_SHARED INetEngine* CreateNetEngine()
{
    return INetEngine::GetInstance();
}

XNETENGINE_SHARED void DestroyNetEngine(INetEngine* instance)
{
    INetEngine::DestroyInstance(instance);
}

XNetEngine::XNetEngine(void)
{
    //...
}

XNetEngine::~XNetEngine(void)
{
    //...
}

//...

  至此,可以通过导入CreateNetEngine、DestroyNetEngine两个函数来使用模块,所有的接口定义在虚基类中,使用的时候带上INetEngine.h这样的头文件就能正确的使用创建返回的接口。从dll导出中只看到两个,实现了一定程度的接口隐藏。最后,如果有大量这样的模块,每次写代码加载两个函数也是非常不畅快的事情。于是我采用了宏定义的方法将加载动作函数一口气写在了虚基类头文件中,并且再用一个宏定义,使这套方便使用的宏函数只在需要的地方释放。

宏函数的定义

//使我们特定形式的dll由静态加载成为动态加载
//BODY指定实体名字,FUNC指定dll导出函数的主题名字,DLL指定最终DLL文件名
#define ImplictLibraryLoad(BODY,FUNC,DLL)    \
extern "C"\
{\
    typedef I##BODY* (*ptr_Create##BODY)();\
    typedef void (*ptr_Destroy##BODY)(I##BODY* instance);\
}\
static int count##BODY = 0;\
static QLibrary lib_##BODY;\
static ptr_Create##BODY func_Create##BODY=0;\
static ptr_Destroy##BODY func_Destroy##BODY=0;\
static I##BODY* DyCreate##BODY(QString path="")\
{\
    I##BODY* i##BODY=0;\
    if(!func_Create##BODY)\
    {\
        if(!lib_##BODY.isLoaded() && !_i_LoadLibrary(lib_##BODY,#DLL,path))return 0;\
        func_Create##BODY = (ptr_Create##BODY)lib_##BODY.resolve("Create"#FUNC);\
        if(!func_Create##BODY)return 0;\
    }\
    i##BODY = func_Create##BODY();\
    if(i##BODY)count##BODY++;\
    return i##BODY;\
}\
static void DyDestroy##BODY(I##BODY* instance)\
{\
    if(!func_Destroy##BODY)\
    {\
        if(!lib_##BODY.isLoaded())return;\
        func_Destroy##BODY = (ptr_Destroy##BODY)lib_##BODY.resolve("Destroy"#FUNC);\
        if(!func_Destroy##BODY)return;\
    }\
    if(instance)count##BODY--;\
    func_Destroy##BODY(instance);\
    if(count##BODY==0)\
    {\
        func_Create##BODY=NULL;\
        func_Destroy##BODY=NULL;\
    }\
    return ;\
}

在虚接口INetEngine.h最后增加这样的定义

//导出函数
#if defined(HIDDEN_DYNAMIC_LOAD_NETENGINE)  //在导出dll中并不包含这些内容
ImplictLibraryLoad(NetEngine,NetEngine,C4ModeNet);
#endif

使用的时候,只需:

#define HIDDEN_DYNAMIC_LOAD_NETENGINE
#include "INetEngine.h"

INetEngine *Engine = DyCreateNetEngine();
if(Engine)DyDestroyEngine(Engine);

是的没看错,Dy前缀的函数是宏函数里声明的。前面做了那么多工作,只为使用时像普通函数一样,真是功德无量……