跨平台插件式设计C++导出类

背景

插件式设计架构并不算是很新的技术了,应对模块化业务的需求还是很有用的,所以今天整理一下,最近也需要将自己写的一些类库进行升级:使用C++17新特性;做一个回顾吧。如何利用C++插件式设计模式来设计服务。

 

插件式系统优、缺点

相较传统的系统架构优点:

1)具有可替换性,同名插件的升级

2)可拓展性

3)模块化功能,定位快,利于修复解决BUG

4)二进制兼容,以动态库的形式存在

 

缺点:

1)做到不重新编译主程序情况下,对外接口不灵活

 

插件式系统核心思想

1.统一导出符号(对外接口统一)

2.统一管理(注册、加载、启动、卸载)

3.一个功能模块一个插件(可拓展性)

 

核心构成

1.看一下网上其他博主的实现,主程序在获得基类对象指针的时候,每一个插件都需要写一个create()去创建指向子类对象的指针,却没有想到用“模板”这个特性

2.主程序中提供基类,并为这些基类定义明确的接口,然后在插件(动态库或共享库)中定义派生类,并实现基类中所有的接口。

因此需要实现的类:动载库加载类、插件实现类、插件载入类、插件管理类

 

业务基类代码:

 

 1 #ifndef __SERVER_API_HPP__
 2 #define __SERVER_API_HPP__
 3 
 4 #include "plugin.hpp"//插件实现类
 5 
 6 /// @brief 业务基类 不做实现
 7 class CServerAPI
 8 {
 9 public:
10     CServerAPI();
11 
12     virtual ~CServerAPI();//必须为虚函数
13 
14     /// @brief 接口函数(recv.*.handler.so)
15     virtual int doWork(const char* pMsg, const char* pConfig = NULL) = 0;
16 };
17 
18 #endif 

 

代码说明:1.基类代码注意析构函数必须为虚函数 2.统一插件接口doWork

 

插件实现类代码:

  1 class LIBAPS_API PluginBase
  2 {
  3 public:
  4     PluginBase();
  5     virtual ~PluginBase();
  6 };
  7 
  8 //! 插件信息
  9 struct PluginMetaInfo 
 10 {
 11     const char* iface;
 12     const char* feature;
 13     PluginBase* (*create)();
 14     void (*destroy)(PluginBase* c);
 15 };
 16 
 17 //! 插件载入功能实现
 18 class LIBAPS_API PluginFactoryImpl
 19 {
 20 public:
 21     PluginFactoryImpl(const char* iface);
 22     virtual ~PluginFactoryImpl();
 23 
 24     //! 加载
 25     void loadFile(const string& filePath) throw(Exception);
 26 
 27     //! 卸载
 28     bool unLoadFile(const string& path);
 29 
 30     //! 载整个目录中的插件
 31     void loadDir(const string& path) throw(Exception);
 32 
 33     //! 创建插件中实现的类
 34     PluginBase* create(const string& feature);
 35 
 36     //! 销毁通过create的对象(切勿自行delete)
 37     void destroy(PluginBase* inst);
 38     
 39 private:
 40         struct Plugin;
 41         Plugin* find(const char* feature) const;
 42 
 43         mutable Mutex mLock;
 44         std::string        mIface;
 45         std::list<Plugin*> mPlugins;
 46         std::map<PluginBase*, Plugin*> mPinst;
 47 };
 48  60 
 61      // 插件管理类 此类用于创建指向子类对象的指针
 62      //采用模板编程,不要每个插件都需要去再写一遍指针创建函数
 63 template<class Iface>
 64 class PluginFactory:public PluginFactoryImpl
 65 {
 66 public:
 67     inline PluginFactory():PluginFactoryImpl(typeid(Iface).name())
 68     {}
 69 
 70     inline ~PluginFactory()
 71     {}
 72 
 73     inline Iface* create(const char* feature)
 74     {
 75         Z_ASSERT(feature!=NULL);
 76         PluginBase *pObj = PluginFactoryImpl::create(feature);
 77         Iface *pObjDist = dynamic_cast<Iface*>(pObj);
 78 
 79         Z_LOG_X(eTRACE)<<"PluginBase = #"<<pObj<<", Iface*=#" << pObjDist;
 80 
 81         if(pObj!=NULL && pObjDist==NULL)
 82             Z_LOG_X(eTRACE) << "Convert object to type [" << typeid(Iface).name() << "] failed!";
 83 
 84         return pObjDist;
 85     }
 86     
 87     inline void destroy(Iface* inst)
 88     {
 89         Z_ASSERT(inst!=NULL);
 90         PluginFactoryImpl::destroy(dynamic_cast<PluginBase*>(inst));
 91     }
 92 };

// 内部实现原理
// ----------------------------------------------
// File : /path/to/X.plgin.so/dll
// ZFPT_plugin =
// {
// className1, "className1", create1, destroy1,
// - Object1, Object2, Objec3 ...
// className2, "className2", create2, destroy2,
// - Object4, Object5, Objec6 ...
// className3, "className3", create3, destroy3
// - Object7, Object8, Objec9 ...
// }
// ----------------------------------------------
// 一个PluginFactory只能加载一个className
//
// 平台只使用一种情况: 一个动态库只包含一个类.



// 以下宏纯粹方便使用.
// --------------------------------------------------------------


// 用于类声明
// --------------------------------------------------------------
#define Z_DECL_SO_API static PluginBase* create(); \
static void destroy(PluginBase* plugin);


// 用于实现文件.
// --------------------------------------------------------------
#define Z_IMPL_SO_API(handlerName, className, apiClasssName) \
Z_PLUGINS_BEGIN \
Z_PLUGIN(apiClasssName, handlerName, className) \
Z_PLUGINS_END \
\
PluginBase* className::create()\
{ \
PluginBase*pObj = NULL;\
\
try\
{\
pObj = new className();\
}\
catch (aps::Exception& e)\
{\
fatalError("Exception: file:%s line:%d function:%s [%d] [%s]\n", e.file(), e.line(), e.func(), e.code(), e.what());\
}\
catch (...)\
{\
fatalError("%s", "Unknown error.\n");\
}\
return pObj;\
}\
void className::destroy(PluginBase* plugin)\
{\
delete plugin;\
}
// --------------------------------------------------------------

 93 

代码说明:

1.定义插件信息结构体包含元素:基类类名称、导出插件名称、两个用于创建和销毁指针的函数指针;插件中主要导出的就是这个结构体信息,

在插件代码中:导出如下结构图示例

extern "C" { PluginMetaInfo PLUGIN[] = {
{ typeid(aps::ClassName).name(), "feature", &feature::create, &feature::destroy },
{ 0, 0, 0, 0 }
};
}

2.业务基类需要继承插件基类PluginBase

3.Z_DECL_SO_API 这个宏方便在到处类中声明 create跟destory函数,Z_IMPL_SO_API定义实现

 

 

动态库加载的代码(跨平台)

动态库加载基类:

class LIBAPS_API SharedLibrary
    {
    public:
        enum ldmode_t 
        {
            bindNow,  /*!< 马上加载 */
            bindLazy  /*!< 用到才加载 */
        };
        SharedLibrary();

        void load(string path, ldmode_t mode = bindLazy) throw(Exception);

        ~SharedLibrary()throw();

        //! 从动态库中获取函数地址
        void* getAddr(string symbol) throw();

        std::string getFilePath();

    private:
        SharedLibrary(const SharedLibrary&);
        SharedLibrary& operator=(const SharedLibrary&);

        struct dso_handle_t;
        dso_handle_t* mHandle;
        string mFilePath;
    };

分别实现WIN32下跟Linux子类

//WIN32平台下的实现

struct
SharedLibrary::dso_handle_t{}; SharedLibrary::SharedLibrary():mHandle(0) { } string SharedLibrary::getFilePath() { return mFilePath; } void SharedLibrary::load(const string name, ldmode_t mode) throw(Exception) { mFilePath = name; mHandle = (dso_handle_t*)LoadLibrary(name.c_str()); int r = GetLastError(); Z_LOG_X(eTRACE) << formatStr("Loading %s to #%p", name.c_str(), mHandle); if(mHandle == 0) throw Exception(Z_SOURCEINFO,r,formatStr("Fail to load file [%s]: %s", name.c_str(), Toolkit::formatError(r).c_str())); } SharedLibrary::~SharedLibrary()throw() { if(mHandle!=0) { Z_LOG_X(eTRACE) << formatStr("Unload #%p", mHandle); FreeLibrary((HMODULE)mHandle); } } void* SharedLibrary::getAddr(string symbol)throw() { Z_ASSERT(mHandle!=NULL); return GetProcAddress((HMODULE)mHandle,symbol.c_str()); }

 

 1 #ifndef Z_OS_WIN32
 2 #include <dlfcn.h>
 3 #include <errno.h>
 4 #include <stdio.h>
 5 
 6 //Linux下的实现
 7 
 8 using namespace std;
 9 struct SharedLibrary::dso_handle_t {};
10 
11 SharedLibrary::SharedLibrary():
12 mHandle(0)
13 {
14 }
15 
16 std::string SharedLibrary::getFilePath()
17 {
18     return mFilePath;
19 }
20 
21 void SharedLibrary::load(const string name, ldmode_t mode) throw(Exception)
22 {
23     mFilePath = name;
24     
25     //mode = bindNow;
26     int flags = 0;
27 //     switch(mode)
28 //     {
29 //     case bindLazy:
30 //         flags = RTLD_LAZY;
31 //         break;
32 //     case bindNow:
33         flags = RTLD_NOW;
34 //         break;
35 //     }
36 
37     errno = 0;
38     mHandle = (dso_handle_t*)dlopen(name.c_str(), flags);
39     Z_LOG_X(eTRACE) << formatStr("Load %s to #%p", name.c_str(), mHandle);
40     if(!mHandle)
41     {
42         int r = errno;
43         const char*p = dlerror();
44         std::string errmsg = p==NULL?"":p;
45         throw Exception(Z_SOURCEINFO, r, errmsg.c_str());
46     }
47 }
48 
49 SharedLibrary::~SharedLibrary() throw()
50 {
51     if (mHandle!=NULL)
52     {
53         Z_LOG_X(eTRACE) <<formatStr("Unload %p", mHandle);
54         dlclose((void*)mHandle);
55         mHandle = NULL;
56     }
57 }
58 
59 void* SharedLibrary::getAddr(string symbol) throw()
60 {
61    Z_ASSERT(mHandle!=NULL);
62    return dlsym((void*)mHandle, symbol.c_str());
63 }

代码说明:

1.win/posix 两个平台加载动态的区别不做说明

2.getAddr函数获取的是PLUGIN这个符号地址,导出的结构体符号

 

总述

实现步骤:

1) 主程序中创建PluginFactory<业务基类> *mFeLoader = new PluginFactory<业务基类>();

2) 加载插件mFeLoader ->loadFile(插件路径);操作:对插件路径进行校验、加载动态库、获取结构体符号地址,遍历插件信息结构体,同时创建符号地址跟插件的映射关系

3)子类* pHandler = mFeLoader->create(插件名称);操作:创建指向子类对象的基类指针,用dynamic_cast返回转换后的子类指针

4)pHandler->doWork(const char*, "");调用子类功能函数,用mFeLoader->destroy(pHandler);销毁

 

posted @ 2019-07-13 14:38  雷泽Lazy  阅读(432)  评论(0编辑  收藏  举报