编写插件系统?

DTS*_*ode 2 c c++ linux plugins posix

经过数小时的研究,我一无所获,所以我求助于各位好心人,希望能找到解决方案。我将用 C++ 编写一个机器人,并且在某个时候想为它制作一个插件系统。现在我知道我可以为它编写一种脚本语言,但是,我知道可以只编写一个 api 并在运行时动态地将程序链接到它。我的问题是,我如何获得动态链接(就像 hexchat 的插件一样)?是否有任何优雅的解决方案,或者至少是典型设计的理论?

Bas*_*tch 5

在 Linux 和 Posix 系统上,您希望使用dlopen(3) & dlsym(或一些包装这些函数的库,例如GTK、QtPOCO等中的Glib ...)。更确切地说,

构建一个位置独立的代码共享库作为你的插件:

 gcc -fPIC -Wall -c plugin1.c -o plugin1.pic.o
 gcc -fPIC -Wall -c plugin2.c -o plugin2.pic.o
Run Code Online (Sandbox Code Playgroud)

请注意,如果插件是用 C++ 编码的,您将使用它进行编译,g++并且您应该声明插件函数extern "C"以避免名称修改

然后将您的插件链接为

 gcc -shared -Wall plugin1.pic.o plugin2.pic.o -o plugin.so
Run Code Online (Sandbox Code Playgroud)

您可以添加动态库(例如-lreadline,如果您的插件需要 GNU readline,则在上面的命令末尾添加)。

最后,dlopen在主程序中使用完整路径调用,例如

 void* dlh = dlopen("./plugin.so", RTLD_NOW);
 if (!dlh) { fprintf(stderr, "dlopen failed: %s\n", dlerror()); 
             exit(EXIT_FAILURE); };
Run Code Online (Sandbox Code Playgroud)

(通常dlh全局数据)

然后使用dlsym来获取函数指针。因此,在程序和插件代码中包含的某些标头中声明它们的签名,例如

 typedef int readerfun_t (FILE*);
Run Code Online (Sandbox Code Playgroud)

声明一些(通常)全局函数指针

 readerfun_t* readplugfun;
Run Code Online (Sandbox Code Playgroud)

dlsym在插件句柄上使用dlh

 readplugfun = (readerfun_t*) dlsym(dlh, "plugin_reader");
 if (!readplugfun) { fprintf (stderr, "dlsym failed: %s\n", dlerror());
                     exit(EXIT_FAILURE); };
Run Code Online (Sandbox Code Playgroud)

当然,在您的插件源代码(例如在plugin1.cc)中,您将定义

 extern "C" int plugin_reader (FILE*inf) { // etc...
Run Code Online (Sandbox Code Playgroud)

您可以在插件中定义一些构造函数(或析构函数)(请参阅GCC 函数属性);将在dlopen(或dlclose)时间调用。在 C++ 中,您应该简单地使用静态对象。(它们的构造函数在dlopen时间被调用,它们的析构函数在dlclose时间被调用;因此是函数属性的名称)。

在程序调用结束时

 dlclose(dlh), dlh = NULL;
Run Code Online (Sandbox Code Playgroud)

在实践中,您可以进行大量(可能是一百万次)dlopen调用。

您通常希望将您的主程序与 链接,-rdynamic以使其符号从插件中可见。

gcc -rdynamic prog1.o prog2.o -o yourprog -ldl
Run Code Online (Sandbox Code Playgroud)

阅读Program Library HowTo & C++ dlopen mini HowTo & Drepper 的论文:如何编写共享库

最重要的部分是定义和记录插件约定(即“协议”),即dlsym插件中所需的一组(和 API)函数(将被-ed)以及如何使用它们,它们的使用顺序调用,内存所有权策略是什么等。如果您允许使用多个类似的插件,则您的主程序中可能有一些记录良好的钩子,它们调用dlsym相关dlopen-ed 插件的所有-ed 函数。示例:GCC 插件约定GNU make 模块Gedit 插件、...