问题贴:
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#编程语言实现,主要用于打开并处理PDF文件,这涉及到C#与PDF文档交互的技术。下面将详细...
《JavaWeb程序设计任务教程第2版》是一本深度探讨JavaWeb开发的教材,主要针对初学者和进阶者提供全面的知识覆盖。本教程通过详细解答课后习题,帮助读者深入理解JavaWeb编程的核心概念和技术。以下是相关知识点的...
在“黑马程序员Python视频中代码、...这个第二章的学习资源可能包括了上述所有或部分概念的讲解和实践练习,通过观看视频、阅读代码和解答课后习题,你可以深入理解并掌握这些基础知识,为后续章节的学习打下坚实基础。
### 第十五届蓝桥杯大赛软件赛省赛第二场 C/C++ 大学B组试题解析 #### 题目背景及要求概述 蓝桥杯大赛是中国一项知名的计算机类竞赛,旨在选拔和培养优秀的计算机人才。本次比赛为第十五届蓝桥杯大赛软件赛省赛第...
### Java语言程序设计第二版习题解答知识点解析 #### 面向对象软件开发方法的重要意义 面向对象的软件开发方法是一种重要的编程范式,它强调以对象为核心的设计思路,将现实世界的实体映射到软件中,使得软件设计...
综上所述,Vue.js面试题会覆盖这些核心知识点,通过回答这些问题,可以判断面试者是否具备Vue.js开发的基本技能和理解能力。在回答问题时,不仅需要了解概念,还应该结合实际项目经验进行详细阐述。
标题和描述中提到的文件是"辽宁省大连市普兰店区第一中学2021届高三上学期第二阶段考试物理试题 Word版含答案.rar",这实际上是一个压缩文件,里面包含的是一个Word文档,该文档提供了针对高中三年级学生的一次物理...
2. 初始化计数器CX,这里填充了数字4,因为一个字节由4位二进制组成,我们需要四次循环来转换为十六进制。 3. 使用ROL(rotate left)指令逐位左移BX的内容,每次移位后提取低4位(AND操作)。 4. 检查提取的低4位...
- **插件:** 使用订单插件进行操作。 **二、学习资源** - **头条模块:** 官方提供的学习平台,包含文章、视频、线下活动等多种形式。 - **问答板块:** 卖家交流论坛,新手卖家可以在此提问,资深卖家会提供解答。 ...
c++数据结构进阶线段树例题 给定长度为 N的数列 A,...2 x y,把 A[x]改成 y。 对于每个查询指令,输出一个整数表示答案。 来源网址:https://www.acwing.com/problem/content/246/ 难度:一般 时/空限制:1s / 128MB
《Thinking in Java 2nd习题及答案》是学习Java编程的重要参考资料,它基于Bruce Eckel的《Thinking in Java》第二版,提供了书中的习题解答,帮助读者深入理解和掌握Java语言的核心概念与技术。这本书涵盖了Java...
2. **可扩展性**:设计为模块化、插件化,允许添加自定义功能和集成第三方服务,以满足不同场景的需求。 3. **自动化**:Kubernetes能够自动部署、重启、复制和扩展容器,确保服务的高可用性和稳定性。 Kubernetes...
1~31 题目 + 完整解答代码 1.分秒转换 2.字符串转换为整数 3.最大最小数字的差值 4.列表最后一个元素 5.比较字符串长度 6.字符串首尾连接 7.检查复数单词 8.列表唯一的数字 9.range转为列表 10.素数判断 11.元音...
2. **数组与字符串**:码图问题中经常涉及数组的操作,如遍历、查找、排序等。字符串处理也是常见需求,C++中的string类提供了丰富的操作方法。 3. **函数与递归**:函数是组织代码的基本单元,递归则是解决某些...
【本人专注IT领域】:有任何使用问题欢迎随时与我联系,我会及时解答,第一时间为您提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【适合场景】:相关项目设计中...
"百度Android工程师面试题.zip" 这个标题表明这是一份与百度公司面试Android工程师相关的资料集合,通常包含常见的面试问题、技术要点以及可能的解答。这份资料可能涵盖了Android开发的基础到高级知识,旨在帮助应聘...
在第9届全国大学生GIS技能大赛中,下午的试题可能涵盖了GIS技术的多个核心领域,包括空间数据处理、地图制图、空间分析、数据库管理以及GIS应用开发等。 1. **空间数据处理**:这部分可能涉及到矢量数据的编辑和...
- 第二题涉及数据库设计,涵盖E-R模型转换为关系模式,数据库完整性定义,SQL操作,以及权限定义。 - 第三题考察UML(统一建模语言),包括9种UML图的使用,如类图、对象图、用例图等。 - 第四、五题是C语言程序...
Vue面试题第二部分主要涵盖了JavaScript基础到高级的概念,这些知识点对于理解和使用Vue.js框架至关重要。以下是对这些面试题的详细解答: 1. **变量的提升(Hoisting)** 在JavaScript中,变量声明(`var`,`let`...
一级考试作为入门级,主要考察基本的BIM操作和理解能力,而二级考试则要求更加深入的技能和实际应用能力。 2. Revit软件介绍:Revit是Autodesk公司开发的一款BIM软件,广泛应用于建筑设计、结构工程和机电工程领域...