总结
- 签名导出:使用
extern "C"
导出函数对函数名有约束,也可以使用struct
包装所有函数签名
- 版本控制:可以参考Android SDK,引入
minVersion
、maxVersio
n和targetVersion
- 元数据:可以保存在插件代码中,也可以保存在元数据中比如xml
- 插件管理器:利用前向声明提供类型信息,但是管理器失去了通用性,会与API的信息耦合
plugin.h
#ifndef PLUGIN_H
#define PLUGIN_H
typedef void (*PluginOpen)();
typedef void (*PluginClose)();
struct PluginSpec {
PluginOpen Open;
PluginClose Close;
};
#endif
core.cpp
#include <dlfcn.h>
#include <boost/property_tree/xml_parser.hpp>
#include <fstream>
#include <iostream>
#include <map>
#include <string>
#include "plugin.h"
const std::string PLUGIN_SPEC{"PLUGIN_SPEC"};
const int kCoreVersion{3};
class PluginManager {
public:
static constexpr PluginSpec kEmpty{nullptr, nullptr};
int LoadFromXml(std::string name) {
using namespace boost::property_tree;
ptree root;
read_xml(name, root);
try {
auto plugin_nodes = root.get_child("plugins");
// 遍历plugin列表
for (auto it = plugin_nodes.begin(); it != plugin_nodes.end(); ++it) {
//
auto name = it->second.get<std::string>("name");
auto min_version = it->second.get<int32_t>("minVersion");
auto max_version = it->second.get<int32_t>("maxVersion");
// 校验版本
if (min_version > kCoreVersion) {
std::cerr << "LoadFromXml:Failed to validate min version:"
<< "name=" << name << ",core_version=" << kCoreVersion
<< ",min_version=" << min_version << std::endl;
} else if (max_version < kCoreVersion) {
std::cerr << "LoadFromXml:Failed to validate max version:"
<< "name=" << name << ",core_version=" << kCoreVersion
<< ",max_version=" << max_version << std::endl;
} else if (min_version <= kCoreVersion && kCoreVersion <= max_version) {
std::cout << "LoadFromXml:"
<< "name=" << name << ",min_version=" << min_version
<< ",max_version=" << max_version << std::endl;
auto pathname = "./" + name + "_plugin.so";
auto handle = dlopen(pathname.c_str(), RTLD_NOW);
// 打开失败
if (handle == nullptr) {
std::cerr << "LoadFromXml:Failed to open plugin file:" << pathname
<< std::endl;
continue;
}
// 注册PluginSpec
PluginSpec *plugin = reinterpret_cast<PluginSpec *>(
dlsym(handle, PLUGIN_SPEC.c_str()));
plugins[name] = *plugin;
} else {
}
}
} catch (std::exception &ex) {
std::cerr << "LoadFromXml:Failed to parse file:" << ex.what()
<< std::endl;
return -1;
}
return 0;
}
int GetPlugin(const std::string &name, PluginSpec *plugin) {
if (plugins.find(name) != plugins.end()) {
*plugin = plugins[name];
return 0;
}
return -1;
}
std::map<std::string, PluginSpec> ListPlugins() const { return plugins; }
private:
std::map<std::string, PluginSpec> plugins;
};
int main() {
PluginManager pm;
pm.LoadFromXml("./plugin.xml");
auto plugins = pm.ListPlugins();
for (auto &plugin : plugins) {
plugin.second.Open();
}
for (auto &plugin : plugins) {
plugin.second.Close();
}
return 0;
}
plugin.xml
<plugins>
<plugin>
<name>hello</name>
<minVersion>2</minVersion>
<maxVersion>4</maxVersion>
</plugin>
<plugin>
<name>world</name>
<minVersion>2</minVersion>
<maxVersion>3</maxVersion>
</plugin>
</plugins>
hello_plugin.cpp && world_plugin.cpp
#include <iostream>
#include "plugin.h"
void Open() { std::cout << "HelloOpen" << std::endl; }
void Close() { std::cout << "HelloClose" << std::endl; }
PluginSpec PLUGIN_SPEC{Open, Close};
#include <iostream>
#include "plugin.h"
void Open() { std::cout << "WorldOpen" << std::endl; }
void Close() { std::cout << "WordClose" << std::endl; }
PluginSpec PLUGIN_SPEC{Open, Close};
Makefile
CXX_FLAGS=-g -std=c++11
all: main plugins
main:
g++ $(CXX_FLAGS) core.cpp plugin.h -o core -ldl
plugins:
g++ $(CXX_FLAGS) -fPIC -shared hello_plugin.cpp plugin.h -o hello_plugin.so
g++ $(CXX_FLAGS) -fPIC -shared world_plugin.cpp plugin.h -o world_plugin.so
.PHONY: all