插件问题回答第2题

问题贴:[url]http://cloverprince.iteye.com/blog/481307[/url]

[quote]
2. 现有一个主程序用C++语言写成。现在要允许第三方开发人员编写扩展的类,约定第三方开发的类必须包含一个继承自某个已知类(如class FooPlugin)的子类,名称不限。如果要求第三方的类必须与主程序的二进制代码分开发布,把dll或so丢在某个文件夹内即可被动态装载使用,应如何实现?
[/quote]

回答:
和C一样,使用shared object和动态装载。但是不同的是,由于需求中以“类”为插件的单位,类相当于一种数据类型(和算法),而数据类型并不是so中可以储存的东西。所以,我们必须转而储存过程。实现中,在插件中使用“工厂函数”实现对象的创建。工厂函数是一个过程,返回的是已经创建好的对象,这样隐藏了对象的实现细节。而对象的接口定义在.h头文件中,在编译期已经确定了调用方法。

适用范围:

思想适用于任何系统。但,由于C++虚函数表和name mangling的实现问题,要求主程序和插件使用相同编译器的相同版本编译。本示例在Linux+GCC(g++)4.4.1下编译通过。

实现:

下面是目录结构:
[quote].
|-- Makefile
|-- main
|-- main.cpp
|-- plugin-interface.h
`-- plugins
|-- Makefile
|-- goodbyeworld.cpp
|-- goodbyeworld.o
|-- goodbyeworld.so
|-- helloworld.cpp
|-- helloworld.o
`-- helloworld.so
[/quote]

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


仅仅换了编译器而已。


执行:

执行需要的最少文件如下:
[quote].
|-- main
`-- plugins
|-- goodbyeworld.so
`-- helloworld.so
[/quote]

[quote][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
[/quote]

总结:
1. main程序并不了解plugins目录中有多少插件。在运行时列举目录。
2. main程序对每个plugins文件(比如叫helloworld.so)的了解只有:
- helloworld.so中有一个函数叫factory,可以填创建IPlugin实例。
- helloworld.so中实现的该类,实现了IPlugin定义的setName和greet两个方法。其调用通过C++类的虚函数表查询得到,涉及到C++的运行时实现细节。
- 将对象转换成IPlugin类实例的指针,即可利用多态性进行操作,不必关心具体类的实现细节。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值