linux下C++ 插件(plugin)实现技术

简介: 首先定义CPluginHelper对象,调用Load方法加载共享库,其中第一个参数是共享库的路径,第二个参数是共享库的名称,共享库名支持模式匹配,这里表示要加载./plugin目录所有so共享库,当然也可以是某个具体的共享库名。

  应用程序中使用插件技术,有利于日后的版本更新、维护(比如打补丁)和功能扩展,是一种很实用的技术。其最大的特点是更新插件时无需重新编译主程序,对于一个设计良好的应用系统而言,甚至可以做到业务功能的在线升级。本文介绍了linux下用C++实现插件的一个简单实例,希望能对大家有所启发。

为了能做到更新插件时无需重新编译主程序,要求主程序中定义的接口是定死的,而接口的实现被放到了具体的插件中,这样主程序在运行时刻将插件加载进来,就可以使用这些接口所提供的功能了。在面向对象的系统中,各个功能模块被封装到类中,因此在C++中实现插件技术,就需要在主程序中提供基类,并为这些基类定义明确的接口,然后在插件(动态库或共享库)中定义派生类,并实现基类中所有的接口。

我们以计算多边形面积为例,首先定义一个基类CPolygon:

  /* +******************************************************* */ 
  /* +******************************************************* */ 
  /* +******************************************************* */ 
 
  /*  polygon.h  */ 
 
#ifndef __POLYGON_H__
  #define  __POLYGON_H__ 
 
#include  <  iostream  > 
 
  class  CPolygon
  {
 public :

    CPolygon() {} 
 
      virtual   ~ CPolygon() {} 
 
     virtual   double  area( void )  const   =   0 ;
;

  #endif  /* __POLYGON_H__ */ 
 
  /* -******************************************************* */ 
  /* -******************************************************* */ 
  /* -******************************************************* */ 
 

注意基类不一定是虚类(有纯虚函数的类),但是接口一定要定义成虚函数,因为最终主程序是通过基类指针
来调派生类的接口函数,另外如果基类中有资源分配(new)的话,析构函数一定要定义成虚的,否则不会被
调用,造成内存泄漏。

接下来要定义派生类,并放到共享库(triangle.so)中:

  /* +******************************************************* */ 
  /* +******************************************************* */ 
  /* +******************************************************* */ 
 
  /*  triangle.h  */ 
 
#ifndef __TRIANGLE_H__
  #define  __TRIANGLE_H__ 
 
#include  "  polygon.h  " 
#include  <  iostream  > 
 
  class  CTriangle :   public  CPolygon
  {
 public :

     virtual   double  area( void )  const ;

;

  #endif  /* __TRIANGLE_H__ */ 
 
 
  /*  triangle.cpp  */ 
 
#include  " triangle.h " 
 
  extern   " C " 
  {
     void   *  create()
     {
         return   new  CTriangle;
    } 

 

 
  double  CTriangle::area(  void )   const 
  {
    std::cout  <<   " area of triangle "   <<  std::endl;
     return   0 ;

 
  /* -******************************************************* */ 
  /* -******************************************************* */ 
  /* -******************************************************* */ 
 

其中定义了函数“create”用来创建CTriangle类对象,该函数可让主程序获得CTriangle对象指针,从而
可以访问CTriangle类对象。主程序通过调用dlsym获取指向该函数的指针,需要指出的是,由于dlsym被
设计成c-style方式,因此调用c++定义的函数时,需要加上extern "C"

那么主程序是如何调用共享库的呢,代码片段如下:

  /* +******************************************************* */ 
  /* +******************************************************* */ 
  /* +******************************************************* */ 
 
typedef CPolygon *  create_t();

  void   *  handle  =  dlopen( " triangle.so " , RTLD_LAZY);

  if (  ! handle )
  {
 std::cerr  <<  dlerror()  <<  std::endl;
 exit( 1 );

 
create_t  *  create_triangle  =  (create_t  * )dlsym(handle,  " create " );

CPolygon  *  pObj  =  create_triangle();

  if (  0   !=  pObj )
  {
 pObj -> area();

 
delete pObj;

dlclose(handle);

  /* -******************************************************* */ 
  /* -******************************************************* */ 
  /* -******************************************************* */ 
 

主程序通过dlopen打开triangle.so,然后通过dlsym得到库中的函数create指针,调用create后返回了
指向CTriangle类对象的指针,类型是CPolygon的,由于虚函数的多态性, pObj->area() 实际是调用
了CTriangle::area.

好了,插件技术就是这么简单,回顾一下实现过程:写一个基类,定义接口函数,然后在共享库中写
派生类,最后在主程序运行时刻打开共享库(dlopen),并通过create函数得到指向新创建的派生类
对象的指针,然后利用虚函数的多态性,调用派生类的各种方法。


不过进一步使用后你可能会发现,这样实现会有些问题:

1. 每写一个派生类就需要重写一个create函数

注意到CTriangle类实现时定义的create函数必须返回 new CTriangle:

 

  extern   " C " 
  {
     void   *  create()
     {
         return   new  CTriangle;
    } 

 

 

那么如果再建一个类比如CRectangle, create函数必须重写,返回 new CRectangle

这样做一方面麻烦,另外CTriangle、CRectangle两个类不能放到同一个共享库中,否则会编译时刻
提示重复定义错误。


2. 主程序无法判断create函数返回的是哪个类所创建的对象

当只有一个基类(CPolygon)时主程序当然知道返回的是CPolygon派生类的对象指针:
create_t * create_triangle = (create_t *)dlsym(handle, "create");
CPolygon * pObj = create_triangle();

假如有多个基类,根据这些基类派生出不同类型的类时,无法在主程序中判断返回的是那个类的对象。


3. 操作繁琐

没有一个统一的操作界面,实现共享库的加载、卸载、派生类对象的创建,特别是当需要加载一个目录
下所有的共享库时,感觉一个一个地加载太麻烦了,能不能批量加载呢。


通过动态类加载和建立Helper类可以很好地解决上述问题,其中dynclass.h/dynclass.cpp中实现了动态
加载类对象,pluginhelper.h/pluginhelper.cpp实现了Plugin Helper,具体细节见附件。


下面简单介绍一下使用步骤:


1. 首先定义基类(CPolygon),方法同上

2. 在共享库中实现派生类

比如CTriangle:

  /* +******************************************************* */ 
  /* +******************************************************* */ 
  /* +******************************************************* */ 
 
  /*  triangle.h  */ 
 
#ifndef __TRIANGLE_H__
  #define  __TRIANGLE_H__ 
 
#include  "  polygon.h  " 
#include   <  iostream  > 
 
  class  CTriangle :   public  CPolygon
  {
 public :

     virtual   double  area( void )  const ;

;

  #endif  /* __TRIANGLE_H__ */ 
 
 
  /*  triangle.cpp  */ 
 
#include  "  triangle.h  " 
#include  "  dynclass.h  " 
 
DYN_DECLARE(CTriangle);

  double  CTriangle::area(  void )   const 
  {
    std::cout  <<   " area of triangle "   <<  std::endl;
     return   0 ;

 
  /* -******************************************************* */ 
  /* -******************************************************* */ 
  /* -******************************************************* */ 
 


注意到此时派生类的实现(triangle.cpp)中已没有了那个讨厌的create了,被我偷偷放到
dynclass.cpp中了:

  extern   " C " 
  {
     void   *  createByClassName( const   char   *  strClassName)
     {
         return  DYN_CREATE(strClassName);
    } 


 

由于对任何派生类而言,该函数的实现都一样,因此只需要实现一次,对使用者是不可见的,达到
了从派生类中拿走的目的。

另外增加了一个宏:DYN_DECLARE(CTriangle); 参数是类名(这里用到了RTTI),每个派生类对应
一个这样的宏,该类就可以支持类对象的动态加载了,需要包含头文件dynclass.h


2. 在主程序中如何使用

使用起来也非常简单,在主程序(main.cpp)中:

  /* +******************************************************* */ 
  /* +******************************************************* */ 
  /* +******************************************************* */ 
   
 
#include  "  pluginhelper.h  " 
#include  "  polygon.h  " 
 
   
 
CPluginHelper pluginHelper;

pluginHelper.Load(  " ./plugin " ,  " *.so "  );

CPolygon  *  pbase  =  (CPolygon  * )pluginHelper.Create( " CTriangle " );

  if (  0   !=  pbase )
  {
    pbase -> area();

 
delete pbase;

pluginHelper.Unload(  " ./plugin " ,  " *.so "  );

  /* -******************************************************* */ 
  /* -******************************************************* */ 
  /* -******************************************************* */ 
 

首先定义CPluginHelper对象,调用Load方法加载共享库,其中第一个参数是共享库的路径,第二
个参数是共享库的名称,共享库名支持模式匹配,这里表示要加载./plugin目录所有so共享库,
当然也可以是某个具体的共享库名。

随后可以通过CPluginHelper::Create方法,根据类名称创建该类的对象,实现了参数化创建对象
的目的,然后就是对该对象的调用,当不用该对象时,需要调用delete来删除。

最后,调用CPluginHelper::Unload将指定共享库卸载。

目录
相关文章
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
4271 77
|
12月前
|
Linux
在线对Linux进行磁盘扩容的技术指南。
综上所述,Linux磁盘扩容的过程,重要的不仅是技术,更是对每一步骤的深刻理解和投入的爱心。只要手握正确的工具,我们不仅能满足"孩子"的成长需求,还能享受其中的乐趣和成就。
764 10
|
安全 大数据 Linux
云上体验最佳的服务器操作系统 - Alibaba Cloud Linux | 飞天技术沙龙-CentOS 迁移替换专场
本次方案的主题是云上体验最佳的服务器操作系统 - Alibaba Cloud Linux ,从 Alibaba Cloud Linux 的产生背景、产品优势以及云上用户使用它享受的技术红利等方面详细进行了介绍。同时,通过国内某社交平台、某快递企业、某手机客户大数据业务 3 大案例,成功助力客户实现弹性扩容能力提升、性能提升、降本增效。 1. 背景介绍 2. 产品介绍 3. 案例分享
441 1
|
安全 Linux KVM
Linux虚拟化技术:从Xen到KVM
Xen和KVM是Linux平台上两种主要的虚拟化技术,各有优缺点和适用场景。通过对比两者的架构、性能、安全性、管理复杂性和硬件依赖性,可以更好地理解它们的适用场景和选择依据。无论是高性能计算、企业虚拟化还是云计算平台,合理选择和配置虚拟化技术是实现高效、稳定和安全IT环境的关键。
1081 8
|
存储 算法 C++
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
文章详细探讨了C++中的泛型编程与STL技术,重点讲解了如何使用模板来创建通用的函数和类,以及模板在提高代码复用性和灵活性方面的作用。
269 2
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
|
Linux 虚拟化
Vmware 傻瓜式安装(不可不知道的Linux基础知识和技术 01)
本文介绍了VMware虚拟机的下载与安装步骤。首先,通过提供的网盘链接下载VMware安装包。接着,详细描述了安装流程,包括接受协议、选择安装路径(建议避免系统C盘)、取消更新选项等。最后,输入许可证密钥完成安装,并展示了打开虚拟机后的主界面。整个过程简单易懂,适合新手操作。
|
安全 Linux Android开发
Linux CFI (Control-flow integrity)技术相关资料汇总
Linux CFI (Control-flow integrity)技术相关资料汇总
|
存储 监控 Linux
在Linux中,如何进行虚拟化技术的应用?
在Linux中,如何进行虚拟化技术的应用?
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
1865 0
|
算法 C# 开发工具
《黑神话:悟空》背后的编程语言揭秘——超越C++的多元技术融合
【8月更文挑战第27天】在游戏开发领域,一款游戏的成功往往离不开其背后强大的技术支持和编程语言的精妙运用。《黑神话:悟空》作为备受瞩目的国产单机动作游戏,其开发过程不仅涉及了多种编程语言,更是一次技术创新的集中展现。然而,当我们深入探讨其开发语言时,会发现它并非仅依赖于单一的C++,而是融合了多种编程语言的优势,共同铸就了这款游戏的辉煌。
854 0