最近参与的一个pipeline streamer类的项目开发,用到插件化的思想,简单做个随笔;
插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。因为插件需要调用原纯净系统提供的函数库或者数据。很多软件都有插件,插件有无数种。
以上是插件的释义。
参照前文c++11 实现依赖注入,我们可以很容易对系统代码作出灵活的拓展,但是这种拓展始终在一个系统内,通俗来讲可能就在一个project中,实际开发过程中,有可能存在框架开发和具体业务开发隔离的形式,比如notepad++、notepad–、vscode中的插件系统,具体的实现类可能就是一个个插件,由他人开发来实现自己想要的功能,而这些人甚至不访问你的代码库,而这些插件作为单独的库存在,只需要放在指定路径就可动态加载进你的系统中。
这么做的优点就不说了,还是老三样,什么方便维护、方便拓展、降低耦合巴拉巴拉;
那这种我们应该怎么做,把这种插件化的思想融入到我们的代码中呢?
我们要做主要包括以下两点
要实现以上要点,我们经过简单思考可以得出以下解决方案:
参照前文c++11 实现依赖注入sample继续完善,不过从简只做思路参考。
插件接口只定义一个process,只做思路展示:
// 基类,可根据业务修改添加接口
class BaseObject
{
public:
virtual ~BaseObject(){};
virtual void process(std::string &data) = 0;
};
pluginmanger 管理加载运行路径下plugin文件夹里的插件库,根据名字加载动态库
class PluginManager
{
public:
static PluginManager &instance()
{
static PluginManager fac;
return fac;
}
void init();
void uninit();
private:
PluginManager()
{
init();
}
~PluginManager()
{
uninit();
}
void loadAllPlugin();
std::unordered_map<std::string, void *> plugins_;
};
std::vector<std::string> list_files(const std::string &directory_path)
{
std::vector<std::string> result;
DIR *directory = opendir(directory_path.c_str());
unsigned char d_type = DT_REG;
if (directory == nullptr)
{
std::cout << "Cannot open directory " << directory_path;
return result;
}
struct dirent *entry;
while ((entry = readdir(directory)) != nullptr)
{
// Skip "." and "..".
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
{
if (entry->d_type == d_type)
{
result.emplace_back(entry->d_name);
}
}
}
closedir(directory);
return result;
}
void PluginManager::init()
{
loadAllPlugin();
}
void PluginManager::uninit()
{
for (const auto &it : plugins_)
{
if (it.second)
{
dlclose(it.second);
}
}
}
void PluginManager::loadAllPlugin()
{
auto files = list_files("./plugin");
for (const auto &it : files)
{
if (it.find("libplugin") == 0 && !plugins_.count(it))
{
void *dl_handle = dlopen((std::string("./plugin/") + it).c_str(), RTLD_LAZY | RTLD_GLOBAL);
if (!dl_handle)
{
std::cout << "Failed to load plugin " << it << ": " << dlerror() << "\n";
return;
}
std::cout << "Plugin " << it << " loaded!\n";
plugins_[it] = dl_handle;
}
}
}
我们现在开始根据接口实现几个plugin,处理传入的data字符串做处理
插件1,字符串转大写
class Test1 : public AutoRegister<Test1>
{
public:
Test1(){}; // 需要注意派生类需要提供自定义构造函数、或者在程序中有显示构造对象(如new test()
// make_shared()等),才会执行模板注册
void process(std::string &data);
void user_code();
constexpr static char *kPlguinName = "user1_define";
};
void Test1::process(std::string &data)
{
std::cout << kPlguinName << " process " << endl;
std::transform(data.begin(), data.end(), data.begin(), ::toupper);
user_code();
}
void Test1::user_code()
{
std::cout << "this user1 code done" << endl;
}
插件2 加后缀
class Test2 : public AutoRegister<Test2> {
public:
Test2() {}; // 需要注意派生类需要提供自定义构造函数、或者在程序中有显示构造对象(如new test() make_shared()等),才会执行模板注册
void process(std::string& data);
void user_code();
constexpr static char* kPlguinName = "user2_define";
};
void Test2::process(std::string &data)
{
std::cout << kPlguinName << " process " << endl;
data = data + "_suffix";
user_code();
}
void Test2::user_code()
{
std::cout << "this user2 code done" << endl;
}
主函数测试,这里的主函数相当于notepad++的窗口程序
int main(int, char **)
{
PluginManager::instance();
vector<string> input = {"user1_define", "user2_define"};
std::cout << " has " << DIContainer::instance().m_map.size() << " pulgins" << std::endl;
vector<shared_ptr<BaseObject>> handlechain;
handlechain.reserve(input.size());
for (const auto &key : input)
{
handlechain.emplace_back(move(DIContainer::instance().resolve(key)));
}
std::string data = "input_data";
std::cout << "The unprocessed data :" << data << std::endl;
for (const auto &node : handlechain)
{
node->process(data);
}
std::cout << "The processed data :" << data << std::endl;
}
输出:
Plugin libplugin2.so loaded!
Plugin libplugin1.so loaded!
has 3 pulgins
The unprocessed data :input_data
user1_define process
this user1 code done
user2_define process
this user2 code done
The processed data :INPUT_DATA_suffix
以上例程附件,cmake工程
如开头所述,以上sample只能说作为一个plugin 框架设计的一个思路展示,玩具demo性质的玩意转换成真正可用的产品还需要考虑挺多的,我们可以参照notepad+±-的源码。