2018-04-25

总结

  • 签名导出:使用extern "C"导出函数对函数名有约束,也可以使用struct包装所有函数签名
  • 版本控制:可以参考Android SDK,引入minVersionmaxVersion和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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容