应用程序中使用插件技术,有利于日后的版本更新、维护(比如打补丁)和功能扩展,是一种很实用的技术。其最大的特点是更新插件时无需重新编译主程序,对于一个设计良好的应用系统而言,甚至可以做到业务功能的在线升级。本文介绍了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将指定共享库卸载。

原始地址:http://masterdog.blogchina.com/634683.html

linux下C++ 插件(plugin)实现技术的更多相关文章

  1. [后渗透]Linux下的几种隐藏技术【转载】

    原作者:Bypass 原文链接:转自Bypass微信公众号 0x00 前言 攻击者在获取服务器权限后,会通过一些技巧来隐藏自己的踪迹和后门文件,本文介绍Linux下的几种隐藏技术. 0x01 隐藏文件 ...

  2. linux下编译安装vim7.4并安装clang_complete插件

    linux下编译安装vim7.4并安装clang_complete插件 因为debian里软件仓库中下载安装的vim是不支持python写的插件的(可以打开vim,在命令模式先输入:py测试一下),导 ...

  3. 打造linux下的source insight——vim插件安装使用总结

    source insight是windows下的优秀编辑器,适合阅读管理代码,主要有以下功能: 查找函数,变量或者宏的定义. 查找函数,变量或者宏的引用位置. 查找函数被调用的位置 查找某个符号在工程 ...

  4. linux下vim如何配置markdown插件

    在vim中设置markdown语法高亮是一个不错的选择,但是在google中搜索到的很多都是比较老的设置方式,甚至vim插件下载页面都是一个旧的版本.这里总结下我的修改过程,以帮助像我一样纠结的人. ...

  5. Linux下双网卡绑定(bonding技术)

    Linux网卡绑定探析   2013-08-20 15:39:31 现在很多服务器都自带双千兆网口,利用网卡绑定既能增加网络带宽,同时又能做相应的冗余,目前应用于很多的场景.linux操作系统下自带的 ...

  6. linux下常用语言的语法检查插件整理

    linux下常用语言的语法检查插件 可以结合vim语法检查插件syntastic使用,具体请参考syntastic使用说明 如php,sql,json,css,js,html,shell,c等语法插件 ...

  7. Linux下的反调试技术

    Linux下的反调试技术 2014年01月30日 ⁄ 综合 ⁄ 共 2669字 ⁄ 字号 小 中 大 ⁄ 评论关闭 转自  http://wangcong.org/blog/archives/310 ...

  8. Windows与Linux下进程间通信技术比较

    一般我们写的程序都是以单个进程的方式来运行的,比较少涉及到多进程.特别是在windows下,因为Windows是按照线程来分配CPU时间片的,线程是最小的调度单位,所以在Windows下更多的用到多线 ...

  9. 在Firefox中发现一个在Linux下查看chm文档的插件

    在Firefox浏览器插件中搜索插件chmfox插件,安装后就可以在linux下通过Firefox浏览器阅读chm文档了.

随机推荐

  1. BZOJ4699 : 树上的最短路

    这道题主要是要解决以下两个问题: 问题1: 给定一个点$x$,如何取出所有经过它的下水道? 一条下水道经过$x$等价于它起点在$x$的子树里面且终点不在$x$的子树里面,或者两端点的lca就是$x$. ...

  2. 到底AR初创公司Magic Leap是不是骗子?我看未必

    AR技术和VR技术在今年的发展可谓是日新月异,眼看年末已至,不成想却出现了大新闻.最炙手可热的神秘AR初创公司Magic Leap被硅谷付费媒体The Information(付费读者大多为硅谷资深投 ...

  3. 淘宝UED上关于chrome的transition闪烁问题的解决方案

    前段时间,有同事和会员反馈使用Chrome访问淘宝首页会出现画面闪动的现象,但是我在Mac和Win下面的Chrome都无法重现这个问题,后来重装了一遍Win7下的Chrome Beta版本,终于重现了 ...

  4. osg中的视点控制

    osg中的视点控制 osg的视点控制基类是CameraManipulator, 它是一个虚基类, 有用的方法都跟home有关. 在这个类里面有三个重要的成员变量: osg::Vec3d _homeEy ...

  5. upload控件上传json文件合并的两种方法

    方法一: byte[] byte1 = FileUpload1.FileBytes; byte[] byte2 = FileUpload2.FileBytes; byte[] a1 = Encodin ...

  6. 【Go语言】LiteIDE使用的个人使用方法

    Go语言开发 可以使用的IDE很多 (Goclipse,sublime,notepad++,vim等)目前使用的最顺手的就是LiteIDE了 但是尽管这样,一开始使用LiteIDE也有很多不习惯的地方 ...

  7. odoo 动态创建字段的方法

    动态创建字段并非一个常见的的需求,但某些情况下,我们确实又需要动态地创建字段. Odoo 中创建字段的方法有两种,一种是通过python文件class中进行定义,另一种是在界面上手工创建,odoo通过 ...

  8. Android studio快捷键总结

    功能 快捷键   功能 快捷键 格式化代码 command + alt + L   运行程序到设备 Ctrl + R 导包.修正 alt + enter   运行Debug到设备 Ctrl + D 复 ...

  9. 对Oracle数据库坏块的理解

    1.物理坏块和逻辑坏块 在数据库中有一个概念叫做数据块的一致性,Oracle的数据块的一致性包括了两个层次:物理一致性和逻辑一致性,如果一个数据块在这两个层次上存在不一致性,那就对应到了我们今天要要说 ...

  10. 微信APP支付(Java后台生成签名具体步骤)

    public class PayCommonUtil { //定义签名,微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序 public static String ...