跨平台插件式设计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);销毁