当你有重复的符号时如何可靠地使用dlsym?

晚上好,我现在正在基于Plux.net模型在C ++ / Linux上开发一个插件系统。

为了简单起见,我用extern C(unmangle)基本上声明了一个符号(让它叫做pluginInformation),我的插件pipe理器在预configuration的导入(.so)中查找这个符号。

事情是,主要的应用程序声明相同的符号,不仅如此,但它的任何依赖也可能有符号。 (因为在这个插件信息中,模块可以发布插件和/或插槽)。

所以当我的PluginManager启动时,它首先尝试在主程序中find符号(将NULL传递给dlopen ),然后尝试在它的任何依赖关系中find符号(使用dl_iterate_phdr )。 最后,它将打开一组configuration导入(它将读取用户configuration的.so的path,将它们打开,最后使用pluginInformation符号)。

在所有模块中find的pluginInformation的集合被用来构build扩展名三。

如果我在主程序中声明符号并使用dlopen加载导入,它将起作用(只要在导入导入时通过标记RTLD_DEEPBIND)。

但是对于应用程序的依赖关系,我没有select传递标志(我可以,但它没有做任何事情),因为这个.sos是在应用程序启动时加载的。

现在,当我尝试使用从依赖关系获得的任何符号(启动时加载的符号)时,出现了分段错误。 我假设问题是我在符号表中有几个同名的符号,奇怪的是,它似乎正确地识别出有几个符号,它甚至给我正确的.so符号的path声明,但只要我访问符号分段错误发生。 如果我只在主程序或其中一个依赖关系中声明符号,那么所有东西都可以正常工作。

我如何pipe理主程序之间的重复符号和使用dlsym?进行导入?

我一直在考虑保持mangling,然后试图find符号表符号,但我不知道这甚至可能(以编程方式列出模块中的所有符号)。

PD:对不起,我没有发布任何代码,但我现在不在家,我希望我所做的努力的描述足够清楚,如果不是,我明天可以发布一些代码。

这是另一种方法。

应用程序本身导出一个或多个插件项目注册功能。 例如:

 int register_plugin_item(const char *const text, const char *const icon, void (*enter)(void *), void (*click)(void *), void (*leave)(void *), void *data); 

每个注册项目有两个字符串插槽( texticon ),三个功能插槽( enterclickleave )以及一个不透明的引用,在调用时作为参数提供给函数。

(请注意,在编译主应用程序(实现上述函数的目标文件)时,需要使用-rdynamic编译器选项,以确保链接器将register_plugin_item符号添加到动态符号表中。

每个插件在一个构造函数(在库加载时自动运行register_plugin_item()调用它所需的每个项目的register_plugin_item()函数。 功能首先检查其运行的环境,以确定要注册哪些功能,或为每个插件项目使用哪些优化的功能变体,这是可能的,而且通常是有用的。

这里是一个简单的例子插件。 注意所有符号是如何static ,这样插件不会污染动态符号表,或者导致任何符号冲突。

 #include <stdlib.h> #include <stdio.h> extern int register_plugin_item(const char *const, const char *const, void (*enter)(void *), void (*click)(void *), void (*leave)(void *), void *); static void enter(void *msg) { fprintf(stderr, "Plugin: Enter '%s'\n", (char *)msg); } static void leave(void *msg) { fprintf(stderr, "Plugin: Leave '%s'\n", (char *)msg); } static void click(void *msg) { fprintf(stderr, "Plugin: Click '%s'\n", (char *)msg); } static void init(void) __attribute__((constructor)); static void init(void) { register_plugin_item("one", "icon-one.gif", enter, leave, click, "1"); register_plugin_item("two", "icon-two.gif", enter, leave, click, "2"); } 

上面的插件导出两个项目。 为了测试,创建至少两个以上的变种; 即使插件使用相同(静态)变量和函数名称,也会看到没有符号冲突。

这是一个加载指定插件的示例应用程序,并测试每个注册项目:

 #include <stdlib.h> #include <dlfcn.h> #include <string.h> #include <errno.h> #include <stdio.h> struct item { struct item *next; const char *text; const char *icon; void *data; void (*enter)(void *); void (*leave)(void *); void (*click)(void *); }; static struct item *list = NULL; int register_plugin_item(const char *const text, const char *const icon, void (*enter)(void *), void (*click)(void *), void (*leave)(void *), void *data) { struct item *curr; curr = malloc(sizeof *curr); if (!curr) return ENOMEM; curr->text = text; curr->icon = icon; curr->data = data; curr->enter = enter; curr->leave = leave; curr->click = click; /* Prepend to list */ curr->next = list; list = curr; return 0; } int main(int argc, char *argv[]) { int arg; void *handle; struct item *curr; if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { fprintf(stderr, "\n"); fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]); fprintf(stderr, " %s PLUGIN.so ... \n", argv[0]); fprintf(stderr, "\n"); fprintf(stderr, "Please supply full plugin paths, unless\n"); fprintf(stderr, "the plugins reside in a standard library directory,\n"); fprintf(stderr, "or in a directory listed in LD_LIBRARY_PATH.\n"); fprintf(stderr, "\n"); return 1; } for (arg = 1; arg < argc; arg++) { handle = dlopen(argv[arg], RTLD_NOW); if (handle != NULL) fprintf(stderr, "%s: Loaded.\n", argv[arg]); else fprintf(stderr, "%s.\n", dlerror()); /* Note: We deliberately "leak" the handle, * so that the plugin is not unloaded. */ } for (curr = list; curr != NULL; curr = curr->next) { if (curr->text) printf("Item '%s':\n", curr->text); else printf("Unnamed item:\n"); if (curr->icon) printf("\tIcon is '%s'\n", curr->icon); else printf("\tNo icon\n"); if (curr->data) printf("\tCustom data at %p\n", curr->data); else printf("\tNo custom data\n"); if (curr->enter) printf("\tEnter handler at %p\n", curr->enter); else printf("\tNo enter handler\n"); if (curr->click) printf("\tClick handler at %p\n", curr->click); else printf("\tNo click handler\n"); if (curr->leave) printf("\tLeave handler at %p\n", curr->leave); else printf("\tNo leave handler\n"); if (curr->enter || curr->click || curr->leave) { printf("\tTest calls:\n"); if (curr->enter) curr->enter(curr->data); if (curr->click) curr->click(curr->data); if (curr->leave) curr->leave(curr->data); printf("\tTest calls done.\n"); } } return 0; } 

如果应用程序是app.c ,并且有插件plugin-foo.cplugin-bar.c ,则可以使用eg

 gcc -W -Wall -rdynamic app.c -ldl -o app gcc -W -Wall -fpic -c plugin-foo.c gcc -shared -Wl,-soname,plugin-foo.so plugin-foo.o -o plugin-foo.so gcc -W -Wall -fpic -c plugin-bar.c gcc -shared -Wl,-soname,plugin-bar.so plugin-bar.o -o plugin-bar.so 

并运行使用例如

 ./app --help ./app ./plugin-foo.so ./app ./plugin-foo.so ./plugin-bar.so 

请注意,如果同一个插件被定义不止一次,构造函数仅针对该库执行一次。 将不会有重复的注册。


插件和应用程序之间的接口完全取决于您。 在这个例子中,只有一个函数。 一个真正的应用程序可能会有更多。 应用程序还可以导出其他功能,例如插件查询应用程序配置。

设计一个好的界面是一个完全不同的话题,并且至少应该和你在实现中一样多。

Plux.NET插件平台允许插件导出他们自己的插槽。 这种替代方法允许在许多方面。 其中之一是导出一个插件注册函数 – 也就是说,注册插件,而不是单个项目 – 这需要一个函数指针:

 int register_plugin(const char *const name, int (*extend)(const char *const, ...)); 

如果插件提供了插槽,它提供了自己的注册函数作为extend函数指针。 例如,应用程序也会导出一个函数

 int plugin_extend(const char *const name, ...); 

该插件可以用来调用其他插件的注册功能。 (在主应用程序中执行plugin_extend()包括搜索已经注册的合适的extend函数,然后调用它/他们。)

按照实施方式,允许插件导出插槽使实现相当复杂。 特别是插件输出插槽的时间和顺序是否可用? 是否有一个特定的插件必须加载的顺序,以确保所有可能的插槽导出? 如果有循环依赖,会发生什么? 插件是否应该在注册之前指定他们依赖的其他插件?

如果每个插件都是一个独立的实体,不会自行导出任何插槽,只能插入主应用程序插槽,这样可以避免实现中的大部分复杂性。

但是,检查注册项目的顺序是您可能需要考虑的一个细节。 上面的示例程序使用链接列表,其中项目以与注册顺序相反的顺序结束,并且注册顺序与在命令行上首先指定插件文件名称的顺序相同。 如果你有一个自动扫描的插件目录(例如使用opendir() / readdir() / dlopen() / closedir()循环),那么插件注册顺序是半随机的(取决于文件系统;通常只改变当插件被添加或删除)。

更正? 有问题吗? 注释?