`
cloverprince
  • 浏览: 127390 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

插件问题回答第2题

阅读更多
问题贴: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类实例的指针,即可利用多态性进行操作,不必关心具体类的实现细节。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics