UNIX下C++实现动态载入对象
发布日期:2021-10-03 22:59:37
浏览次数:26
分类:技术文章
本文共 4584 字,大约阅读时间需要 15 分钟。
VC里面实现动态对象载入已经不是什么新鲜事情了,很多的plug-in技术就是例子。Unix下,通过动态载入so获得一个对象也不是什么难事,不过对这个对象的管理就是一件比较麻烦的事情了。一般的需求如下:
有class TMyObj,准确说TMyObj应该是一个接口,根据不同具体情况会有不同的实现,例如 TMyObj1、TMyObj2等等……而这些TMyObj1和TMyObj2分别保存在不同的so当中,需要根据不同的时候load不同的so,建立相应的对象。由于这些对象都拥有TMyObj的接口,所以对于外部来说对这些类的使用就像对TMyObj的使用一样。 看起来好像比较简单,只要在so里面引出一个函数: TMyObj * onCreateObject( void);
而函数在so中的具体实现就是建立不同的子类,例如在obj1.so中:
TMyObj * onCreateObject( void) { return new TMyObj1; }
使用的时候只需要动态load入obj1.so,并且找到onCreateObject函数的入口,就可以建立一个具有TMyObj接口的TMyObj1了。 至于释放对象,一般有两种方法: 方法一: so中包含另外一个函数:
void onDestroyObj( void * p) { TMyObj1 * tp = (TMyObj1 *)p; delete tp; }
从so中导出该函数,并在删除对象的时候调用。 方法二: TMyObj的析构函数声明为虚函数,那么从so导出的onCreateObject()建立的对象,直接执行delete删除就行了,由于析构函数是虚函数,编译器会正确的调用TMyObj1的析构函数。 当然,方法二是比较简单而优雅的方法,既然对于C++来说接口就相当于纯虚函数,多增加一个析构的虚函数又何妨呢。但是无论使用哪种方法,都要注意一个问题,就是载入的obj1.so的生命周期要比最后一个TMyObj1的生存周期长。即只要内存中还存在TMyObj1对象,obj1.so就要一直在内存中,不能卸载。要保证这个同步,是比较麻烦的事情。下面就说说我的解决方法: 首先,要选择一个通用的载入so的lib,这个可以参考一下common c++的DSO(在file.h)里面。(不想使用common c++?我也只是说“参考”而已)。这个支持DLL和so,通过成员函数void *operator[](const char *);获得指定的symbol的入口。 其次,就要选择一个通用的SmartPtr。这个当然Loki是首选,Loki的SmartPtr的灵活性比boost的smart_ptr强多了,而且Loki也小巧的多。 然后就要实现一个简单的so的manager,其实应该说是一个动态object的factory:
class TObjFactory : protected DSO { public: TObjFactory(void); void load(const std::string & strPath); void * createObj(void) const throw (TSOException); protected: typedef void * (*funcCreate)(void ** p); funcCreate m_pCreator; };
可以想象这个类干些什么:load就是载入相应的so,然后获得so中onCreateObject函数的入口,并赋给成员m_pCreator。而createObj就是调用m_pCreator建立对象。不过有所不同的是 m_pCreator所指向的函数形式是void * funcCreate(void ** p),而多出来void **p用处就是可以让so中的构造函数中产生的exception能够传递出来。这个不能说不是so的麻烦之处,so中函数的exception不能被外部捕获,所以只好这样子做了。 现在,关键的地方来了,就是要保证这个TObjFactory的生存周期了。选择Loki的SmartPtr就能派上用场了。 Loki的SmartPtr可以自己选定适用的StoragePolicy,这正是我们需要的,参考DefaultSPStorage,可以做我们的TMySOStoragePolicy:
template< class T> class TMySOStoragePolicy { .. protected: void Destroy() { delete pointee_; m_pFactory = SmartPtr<TObjFactory>(); } private: SmartPtr<TObjFactory> m_pFactory; StoredType pointee_; };
显而易见,这样做的目的就是要保证释放指针的时候就减少TObjFactory的引用计数。
好了,现在就是主角了: template< class T> class TDObj : public SmartPtr<T,RefCounted,DisallowConversion,AssertCheck,TMySOStoragePolicy> { public: TDObj(void); TDObj(const TDObj & obj); .. protected: friend class TDObjManager; TDObj(T * p, SmartPtr<TObjFactory> pManager); }; class TDObjManager { public: template<class T> static TDObj<T> createObj(const std::string & strKeyName) { SmartPtr<TObjFactory> pFactory = getFactoryByName(strKeyName); //这里面可以做很多事情了,例如访问内存,查找相应的Factory;或者读取配置文件、读入新的so并建立新的Factory。 //或者根据一些淘汰算法,先淘汰内存的Factory,然后重新载入新的Factory等等。 std::auto_ptr<T> _au( static_cast<T *>(pFactory->createObj()) ); return TDObj<T>( _au.release(), pFactory); } };
以后用起来就简单多了:
class TMyObj { public: virtual ~TMyObj(void); virtual int func(void) = 0; }; TDObj<TMyObj> obj1 = TDObjManager::createObj<TMyObj>( "obj1.so") ); TDObj<TMyObj> obj2 = TDObjManager::createObj<TMyObj>( "obj2.so") ); cout << obj1->func() << endl; cout << obj2->func() << endl;
说了这么久,都是主程序的调用,而so中应该如何呢?其实也很简单:
class TMyObj1 : public TMyObj { public: TMyObj1(void); ~TMyObj1(void); static void onStaticInit(void); static void onStaticDestroy(void); static const char * getVersion(void); static const char * getObjectName(void); virtual int func(void); }; DECLARE_SO_INTERFACE(TMyObj1);
DECLARE_SO_INTERFACE其实是一个为了方便编写程序而定义的宏:
#define DECLARE_SO_INTERFACE(x) extern "C" { \ void onInstallDLL( void); \ void onUninstallDLL( void); \ const char * onGetVersion( void); \ const char * onObjectName( void); \ void * onCreateObject( void ** ppException); \ }; \ void onInstallDLL( void) { x::onStaticInit(); } \ void onUninstallDLL( void) { x::onStaticDestroy(); } \ const char * onGetVersion( void) { return x::getVersion(); } \ const char * onObjectName( void) { return x::getObjectName(); } \ void * onCreateObject( void ** pException) { \ try { \ *pException = NULL; x * p = new x(); return (void *)p; \ }catch(std::exception & e) { \ *pException = new std::exception(e); \ return NULL; \ } \ }
可以看到除了导出onCreateObject函数以外,还导出了: TMyObj1::onStaticInit用于载入so的时候执行初始化操作; TMyObj1::onStaticDestroy用于卸载so的时候执行清理操作; TMyObj1::getVersion 获得对象的版本信息 TMyObj1::onObjectName 获得对象名信息等 可以扩展前面的TObjFactory,实现这些功能。 同理,我们可以做obj2.so:
class TMyObj2 : public TMyObj { public: TMyObj2(void); ~TMyObj2(void); static void onStaticInit(void); static void onStaticDestroy(void); static const char * getVersion(void); static const char * getObjectName(void); virtual int func(void); }; DECLARE_SO_INTERFACE(TMyObj2);
另外,一个值得讨论的问题是:C++由于没有反射机制,所以无法实现设值注入和构造注入,只能实现接口注入。不过一般来说也已经足够使用了。
转载地址:https://blog.csdn.net/lclflash/article/details/8856800 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
感谢大佬
[***.8.128.20]2024年04月07日 02时59分17秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
python 数据科学 - 【回归分析】 ☞ 线性回归(2)
2019-04-27
设计模式——工厂模式
2019-04-27
Unity中实现有限状态机FSM
2019-04-27
Unity中实现反弹
2019-04-27
U3D游戏开发框架(九)——事件序列
2019-04-27
Unity中解决“SetDestination“ can only be called on an active agent that has been placed on a NavMesh
2019-04-27
Unity中的刚体
2019-04-27
Unity中的坐标转换
2019-04-27
Unity中为什么不能对transform.position.x直接赋值?
2019-04-27
使用对象池优化性能
2019-04-27
Lua(一)——Lua介绍
2019-04-27
Lua(二)——环境安装
2019-04-27
Unity中父子物体的坑
2019-04-27
基础知识——进位制
2019-04-27
Lua(十二)——表
2019-04-27
Lua(十三)——模块与包
2019-04-27
Lua(四)——变量
2019-04-27
Lua(十四)——元表
2019-04-27
Lua(十五)——协同程序
2019-04-27
Lua(十六)——文件
2019-04-27