一种方便的动态库动态加载机制

本文介绍了一种简化动态加载动态库的过程,通过封装模块为类并定义纯虚函数,实现动态库的导入与导出。这种方法利用宏定义减少代码冗余,提高模块的复用性和可维护性。此外,提出了一套方便使用的宏函数,使得动态库的导入与销毁操作更加简洁。通过实例展示了如何在C++环境中实现这一机制,包括单例定义、虚接口定义、模块功能实现以及动态加载宏定义,最终达到无缝集成动态库的目的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  动态加载动态库在很多时候的用户体验都比较好,可以检查发现缺失的文件,可以让可选的模块缺失而继续工作。但是动态加载涉及很多函数定义,函数寻址。写来写去非常繁琐且没有技术含量,遇到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前缀的函数是宏函数里声明的。前面做了那么多工作,只为使用时像普通函数一样,真是功德无量……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值