问题贴:
http://cloverprince.iteye.com/blog/481307
引用
2. 现有一个主程序用C++语言写成。现在要允许第三方开发人员编写扩展的类,约定第三方开发的类必须包含一个继承自某个已知类(如class FooPlugin)的子类,名称不限。如果要求第三方的类必须与主程序的二进制代码分开发布,把dll或so丢在某个文件夹内即可被动态装载使用,应如何实现?
回答:
和C一样,使用shared object和动态装载。但是不同的是,由于需求中以“类”为插件的单位,类相当于一种数据类型(和算法),而数据类型并不是so中可以储存的东西。所以,我们必须转而储存过程。实现中,在插件中使用“工厂函数”实现对象的创建。工厂函数是一个过程,返回的是已经创建好的对象,这样隐藏了对象的实现细节。而对象的接口定义在.h头文件中,在编译期已经确定了调用方法。
适用范围:
思想适用于任何系统。但,由于C++虚函数表和name mangling的实现问题,要求主程序和插件使用相同编译器的相同版本编译。本示例在Linux+GCC(g++)4.4.1下编译通过。
实现:
下面是目录结构:
引用
.
|-- Makefile
|-- main
|-- main.cpp
|-- plugin-interface.h
`-- plugins
|-- Makefile
|-- goodbyeworld.cpp
|-- goodbyeworld.o
|-- goodbyeworld.so
|-- helloworld.cpp
|-- helloworld.o
`-- helloworld.so
plugin-interface.h中定义了接口。
/* plugin-interface.h */
#ifndef _PLUGIN_INTERFACE_H_
#define _PLUGIN_INTERFACE_H_
#include <string>
class IPlugin { 这个虚基类是接口。
public:
virtual void setName(std::string name)=0; // 设置名字
virtual void greet()=0; // 打招呼
};
extern "C" {
typedef IPlugin* (*PluginFactoryFunc)(); // 工厂函数,创造一个IPlugin实例
}
#endif
/* end of plugin-interface.h */
plugin目录中放置多个插件。插件的实现方法如下:
/* plugins/helloworld.cpp */
#include <iostream>
#include <string>
#include "../plugin-interface.h"
using namespace std;
class HelloWorldPlugin : IPlugin { // 一个插件的具体实现
string name;
public:
void setName(string name) { // 重写(override)了虚函数
this->name = name;
}
void greet() {
cout<<"Hello, "<<name<<endl;
}
};
extern "C" {
IPlugin* factory() { // 工厂函数。
return (IPlugin*)(new HelloWorldPlugin());
}
}
/* end of plugins/helloworld.cpp */
另一个插件类似:
/* plugins/goodbyeworld.cpp */
#include <iostream>
#include <string>
#include "../plugin-interface.h"
using namespace std;
class GoodbyeWorld : IPlugin {
string name;
public:
void setName(string name) {
this->name = name;
}
void greet() {
cout<<"Goodbye, "<<name<<endl;
}
};
extern "C" {
IPlugin* factory() {
return (IPlugin*)(new GoodbyeWorld()); // 工厂创建不同的对象
}
}
/* end of plugins/goodbyeworld.cpp */
主程序如下:
/* main.cpp */
#include <iostream>
#include <cstdlib>
#include <string>
#include <vector>
#include <dlfcn.h>
#include <boost/filesystem.hpp> // 使用boost_filesystem库代,更符合c++风格。
#include "plugin-interface.h"
using namespace std;
namespace fs = boost::filesystem;
const int MAX_PLUGINS=10;
fs::path PLUGINS_PATH("plugins");
struct PluginInfo { // 插件记录。每个插件对应一个
string path; // 路径/文件名
void* lib_handle; // 库句柄
PluginFactoryFunc factory; // 工厂函数
};
vector<PluginInfo> plugins;
void load_plugin(string path) {
PluginInfo pi;
char* err;
pi.path = path;
pi.lib_handle = dlopen(path.c_str(), RTLD_LAZY); // 仍然使用dlopen打开so库
err = dlerror();
if(pi.lib_handle==NULL) {
cerr<<"Cannot open "<<path<<": "<<err<<endl;
return;
}
pi.factory = (PluginFactoryFunc)dlsym(pi.lib_handle, "factory"); // 取出工厂函数
err = dlerror();
if(err != NULL) {
cerr<<"Cannot find function 'factory' in "<<path<<": "<<err<<endl;
dlclose(pi.lib_handle);
return;
}
plugins.push_back(pi); // 储存对象记录
cerr<<"Plugin successfully loaded: "<<path<<endl;
}
int main() {
fs::directory_iterator end_iter;
for(fs::directory_iterator dir_iter(PLUGINS_PATH);
dir_iter!=end_iter;
++dir_iter) { // 遍历plugins/*
string filename;
string pathname;
filename = dir_iter->path().filename();
if(filename.length()<3) continue;
if(filename.substr(filename.length()-3,3)!=".so") continue; //检查后缀
pathname = PLUGINS_PATH.filename() + "/" + filename;
load_plugin(pathname); // 装载插件
}
vector<PluginInfo>::iterator it;
for(it=plugins.begin();it!=plugins.end();++it) { // 遍历测试插件
cerr<<"Testing "<<it->path<<" ..."<<endl;
IPlugin *plugin = it->factory(); // 创建实例
plugin->setName("wks"); // 设置名字
plugin->greet(); // 打招呼
delete plugin; // 析构插件对象的实例
}
for(it=plugins.begin();it!=plugins.end();++it) { // 卸载库
dlclose(it->lib_handle);
}
return 0;
}
/* end of main.cpp */
编译:
编译过程和C语言版本类似。
# Makefile
all: main
main: main.cpp plugin-interface.h
g++ -rdynamic -ldl -lboost_filesystem -o $@ $^
# End of Makefile
需要注意的是这里用到了boost_filesystem库
下面是插件的Makefile
# plugins/Makefile
all: helloworld.so goodbyeworld.so
helloworld.so: helloworld.cpp
g++ -c -fPIC helloworld.cpp
g++ -shared -o helloworld.so helloworld.o
goodbyeworld.so: goodbyeworld.cpp
g++ -c -fPIC goodbyeworld.cpp
g++ -shared -o goodbyeworld.so goodbyeworld.o
# end of plugins/Makefile
仅仅换了编译器而已。
执行:
执行需要的最少文件如下:
引用
.
|-- main
`-- plugins
|-- goodbyeworld.so
`-- helloworld.so
引用
[wks@localhost out]$ ./main
Plugin successfully loaded: plugins/goodbyeworld.so
Plugin successfully loaded: plugins/helloworld.so
Testing plugins/goodbyeworld.so ...
Goodbye, wks
Testing plugins/helloworld.so ...
Hello, wks
总结:
1. main程序并不了解plugins目录中有多少插件。在运行时列举目录。
2. main程序对每个plugins文件(比如叫helloworld.so)的了解只有:
- helloworld.so中有一个函数叫factory,可以填创建IPlugin实例。
- helloworld.so中实现的该类,实现了IPlugin定义的setName和greet两个方法。其调用通过C++类的虚函数表查询得到,涉及到C++的运行时实现细节。
- 将对象转换成IPlugin类实例的指针,即可利用多态性进行操作,不必关心具体类的实现细节。
分享到:
相关推荐
第十五届蓝桥杯大赛软件赛省赛第二场C/C++ 大学A B C组试题 对于编程题目,要求选手给出的解答完全符合 GNU C/C++ 标准,不能使 用诸如绘图、Win32API、中断调用、硬件操作或与操作系统相关的 API。 代码中允许使用 ...
c++数据结构进阶线段树例题 给定长度为 N的数列 A,...2 x y,把 A[x]改成 y。 对于每个查询指令,输出一个整数表示答案。 来源网址:https://www.acwing.com/problem/content/246/ 难度:一般 时/空限制:1s / 128MB
第一章计算机控制系统概述习题与思考题1.1 什么是计算机控制系统?计算机控制系统较模拟系统有何优点?举例说明。解答: 由计算机参与并作为核心环节的自动控制系统,
通信原理习题 第六章 数字信号的调制传输共3页,第2页2、已知解调器输入端的峰值信噪比为 8 dB,分别计算 2ASK 和 2PSK 相干解调的误比特率,并进行
通信原理习题 第五章 数字信号的基带传输共 4页,第 2页2、已知某数字代码序列对应的 CMI 码基带信号波形如图所示,画出对应的 HDB3 码基带信号的波形,
(4)对于每道题,学生第一次输入正确答案得10分,第二次输入正确答案得7分,第三次输入正确答案得5分,否则不得分; (5)总成绩90以上显示SMART”80-90显示“GOOD”,70-80显示OK60-70显示“PASS”,60以下“TRYAGAIN
前言:2023年上半年软考已经落幕了,学长整理了一下软件设计师的题目以及个人理解的答案(仅供参考)希望能够帮助参加软考的各个小伙伴能够清晰的估分,...解答时用正规2B铅笔正确填涂选项,如需修改,请用橡皮擦干净,
第1章 微型计算机系统〔习题1.1〕简答题〔解答〕① 处理器每个单位时间可以处理的二进制数据位数称计算机字长。② 总线信号分成三组,分别是数据总线、地址总线和控
一、回答下面的问题 1、什么是超定方程,其对应的珍贵方程组是什么,系数特点是什么? 2、最小二乘拟合方法和插值方法的相同点和不同点,各自用在什么情况下? 3、生成超定方程时,Matlab是行优先,还是列优先? 4、...
第二:鸡蛋交易手续费 解释:用户在里面可以买卖鸡蛋,平台可以收取一定的手续费。用户为什么要交易鸡蛋呢?因为鸡蛋会在里面有各种用户,买道具,荟加商家活动,全部是用 鸡蛋。没有鸡蛋就不能维续玩游戏。而且我们...
因为girlfriend自带了很多插件,依赖的第三方包就比较多,所以如果带宽不够大,安装速度就会比较慢一些,请耐心等待。另外,建议大家最好先通过[virtualenv](https://virtualenv.readthedocs.org/en/latest/)来安装...
改进问答系统问题详情页面如果是专家回答的问题,增加可以显示专家认分类及专家姓名等信息,以区分普通会员及专家回答。评论增加数据表,当默认的评论数据表数据量很大量,会严重的影响页面的加载速度。这时我们可以...
改进问答系统问题详情页面如果是专家回答的问题,增加可以显示专家认分类及专家姓名等信息,以区分普通会员及专家回答。评论增加数据表,当默认的评论数据表数据量很大量,会严重的影响页面的加载速度。这时我们可以...
第二章 开发第一个Java应用程序 46 2.1 介绍 46 2.2 手工编写,编译并运行Java程序 46 2.3 使用Eclipse/MyEclipse来编写,编译并运行Java程序 47 2.4小结 50 第三章 Eclipse 的基础概念,配置和使用 51 3.1界面布局 ...
第44章章栈与队列习题[4-1]第4章 栈不队列88[4-1] a) 试基二 3.2.2 节癿列表模板类 List,实现栈结极;【解答】仿照教材中由Vector
2、改进了用户反馈模块中系统题号不精确问题 3、用户可以自行选择自动升级或不升级 5.3.4版更新内容 1、改进界面,采用循环分形图作背景,黑色背景色 2、增加了“登陆”功能 3、用户在结束考试后,成绩直接提交,并...
改进问答系统问题详情页面如果是专家回答的问题,增加可以显示专家认分类及专家姓名等信息,以区分普通会员及专家回答。评论增加数据表,当默认的评论数据表数据量很大量,会严重的影响页面的加载速度。这时我们可以...
第2章 地图如何表达信息 2.1 地图的用途 2.2 地图如何表达信息 2.3 地图的构成 2.4 用图层来表示地理 显示全部信息 媒体评论 内容加载中,请稍后... 商品评论(3条)购买过的顾客评分 4星心情指数:1人 沉重阅读...