Vel*_*los 6 c c++ linux dlopen dlsym
晚上好,我目前正在使用基于Plux.net模型的C++/Linux插件系统.
为了简单起见,我基本上用extern C(to unmangle)声明一个符号(让我们称之为pluginInformation),我的插件管理器在预先配置的导入(.so)中查找该符号.
问题是主应用程序声明了相同的符号,不仅如此,它所具有的任何依赖关系也可能具有符号.(因为在这个插件信息中,模块可以发布插件和/或插槽).
因此,当我的PluginManager启动时,它首先尝试在主程序中找到符号(将NULL传递给dlopen),然后它尝试在其任何依赖项中找到符号(使用dl_iterate_phdr).最后它将dlopen一组配置导入(它将读取用户配置的.so的路径,dlopen它们,最后dlsym pluginInformation符号).
然后使用在所有模块中找到的pluginInformation的集合来构建扩展三.
如果我在主程序中声明符号并使用dlopen加载导入,它就可以工作(只要我在导入时失去标记RTLD_DEEPBIND).
但是对于应用程序依赖项,我没有选择传递标志(我可以,但它没有做任何事情),因为这个.sos是在应用程序启动时加载的.
现在,当我尝试使用从依赖项中获得的任何符号(启动时加载的符号)时,我会遇到分段错误.我假设问题是我在符号表中有几个同名的符号,奇怪的是它似乎正确地识别出有几个符号,它甚至给我正确的.so路径,其中符号是声明,但是一旦我访问该符号就会发生分段错误.如果我只在主程序或其中一个依赖项中声明符号,则一切正常.
如何使用dlsym管理主程序和strat up导入之间的重复符号?
我一直在考虑保持修剪,然后尝试找到符号表的符号,但我不确定这是否可能(以编程方式列出模块中的所有符号).
PD:对不起,我没有发布任何代码,但我现在不在家,我希望我试图做的描述很清楚,如果不是我可以明天发布一些代码.
这是另一种方法.
应用程序本身导出一个或多个插件项注册功能.例如:
int register_plugin_item(const char *const text,
const char *const icon,
void (*enter)(void *),
void (*click)(void *),
void (*leave)(void *),
void *data);
Run Code Online (Sandbox Code Playgroud)
每登记项目中,有两个串槽(text和icon),三个功能时隙(enter,click,和leave),和调用时被提供给用作参数的不透明参考.
(请注意,-rdynamic在编译主应用程序(实现上述函数的目标文件)时,您需要使用编译器选项,以确保链接器将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");
}
Run Code Online (Sandbox Code Playgroud)
上面的插件导出两个项目.要进行测试,请至少创建以上几种变体; 即使插件使用相同的(静态)变量和函数名,您也会看到没有符号冲突.
这是一个加载指定插件的示例应用程序,并测试每个注册的项目:
#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;
}
Run Code Online (Sandbox Code Playgroud)
如果应用程序是app.c,你有插件plugin-foo.c和plugin-bar.c,则可以使用例如编译它们
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
Run Code Online (Sandbox Code Playgroud)
并使用例如
./app --help
./app ./plugin-foo.so
./app ./plugin-foo.so ./plugin-bar.so
Run Code Online (Sandbox Code Playgroud)
请注意,如果多次定义相同的插件,则只为该库执行一次构造函数.没有重复的注册.
插件和应用程序之间的接口完全取决于您.在此示例中,只有一个功能.一个真正的应用可能会有更多.应用程序还可以导出其他功能,例如,插件可以查询应用程序配置.
设计一个好的界面是一个完全不同的主题,并且至少应该像你在实现中所做的那样考虑.
Plux.NET插件平台也允许插件导出自己的插槽.这种替代方法允许在许多方面.其中之一是导出一个插件注册函数 - 即,用于注册插件 而不是单个项 - 带有一个函数指针:
int register_plugin(const char *const name,
int (*extend)(const char *const, ...));
Run Code Online (Sandbox Code Playgroud)
如果插件提供插槽,它提供自己的注册功能作为extend函数指针.例如,该应用程序还会导出一个函数
int plugin_extend(const char *const name, ...);
Run Code Online (Sandbox Code Playgroud)
插件可用于调用其他插件的注册功能.(plugin_extend()主应用程序的实现涉及搜索extend已注册的合适函数,然后调用它/它们.)
实现方面,允许插件导出插槽使实现变得相当复杂.特别是,插件导出的插槽何时以及以何种顺序变为可用?是否有必须加载插件的特定顺序,以确保导出所有可能的插槽?如果存在循环依赖会发生什么?插件是否应该在注册开始之前指定他们依赖哪些其他插件?
如果每个插件都是一个单独的实体,不会导出自己的任何插槽,只插入主应用程序插槽,则可以避免实现中的大部分复杂性.
但是,检查注册项目的顺序是您可能需要考虑的细节.上面的示例程序使用链接列表,其中项目与注册顺序相比以相反的顺序结束,并且注册顺序与在命令行上首次指定插件文件名称的顺序相同.如果你有一个自动扫描的插件目录(使用例如opendir()/ readdir()/ dlopen()/ closedir()loop),那么插件注册顺序是半随机的(取决于文件系统;通常仅在添加或删除插件时才更改).
更正?有问题吗?评论?
| 归档时间: |
|
| 查看次数: |
1006 次 |
| 最近记录: |