作为此后C插件系统的继续:符号查找错误,我仍在编写我的插件系统,并遇到了新的错误。
概括一下插件是什么,该程序包含一个由外壳接口的网络应用程序,消息具有类型,因此可用于在网络上创建应用程序。例如,可能的应用程序是聊天或转移应用程序。
因此,shell命令可以在网络上发送特定应用程序的消息,当接收到一条消息时,如果它对应于特定应用程序,则将消息内容作为参数执行一个动作功能,它可能就是该应用程序。
插件是一个共享库,带有一个初始化函数,用于注册其命令和动作。命令可能只是一个不与网络交互的简单命令,这就是我目前实现的原因。
插件系统包含以下模块:
网络部分包括:
protocole中的模块都是相互依赖的,为方便起见,我使用了分割文件。所有模块都使用-fPIC选项进行编译。
要编译不与网络交互的名为 plug.c wich 的插件,我使用:
gcc -Wall -O2 -std=gnu99 -D DEBUG -g -fPIC -c -o plug.o plug.c
gcc -Wall -O2 -std=gnu99 -D DEBUG -g -o plug.so plug.o plugin_system.o list.o -shared
它运行完美,库dlopen
没有问题,初始化函数已加载dlsym
并正确执行,因此插件已注册,然后我执行了命令,可以看到它正常工作。
现在,我不想为插件添加对网络通信的支持,因此,我修改了与我使用的相同的测试插件,该测试插件仅具有打印消息的命令。我已经调用了sendappmessage_all
一个函数,该函数通过
message.c中 定义的网络上的每个人发送 消息 。
我可以在不添加网络模块对象的情况下编译新插件,它可以编译,插件可以正确加载,但是当它调用时sendappmessage_all
显然会失败,并显示以下消息
symbol lookup error: ./plugins/zyva.so: undefined symbol: sendappmessage_all
因此,要使其正常工作,我应该喜欢带有网络模块的插件,这就是我所做的
gcc -Wall -O2 -std=gnu99 -D DEBUG -g -o plug.so plug.o plugin_system.o list.o protocol.o message.o thread.o common.o application.o network.o -shared
它可以编译,但是当我尝试加载插件时,dlopen
返回NULL
。我还尝试仅添加一个模块,最糟糕的是只会导致一个undefined symbol
错误,但我dlopen
仍然会返回NULL
。
我知道这是很多信息,另一方面,您可能不想看代码,但我试图以最简洁的方式使代码更清晰,因为它比帖子更复杂,更大。
感谢您的理解。
问题在于,当您编译插件系统(即,由插件调用的函数)并将其链接到最终可执行文件时,链接器不会在动态符号表中导出插件使用的符号。
有两种选择:
-rdynamic
链接最终可执行文件时使用,将所有符号添加到动态符号表中。
-Wl,-dynamic-list,plugin-system.list
在链接最终的可执行文件时使用,将文件中列出的符号添加plugin-system.list
到动态符号表中。
文件格式很简单:
{
sendappmessage_all;
plugin_*;
};
换句话说,您可以列出每个符号名称(函数或数据结构),也可以列出与所需符号名称匹配的全局模式。请记住,每个符号后和右括号后都使用分号,否则在链接时会收到“动态列表中的语法错误”错误。
请注意,仅标记功能为“使用”的通道__attribute__((used))
不足以使链接器将其包括在动态符号表中(至少在GCC 4.8.4和GNU ld 2.24中)。
由于OP认为我在上面写的是不正确的,所以这里是上述的完全可验证的证明。
首先,一个简单的 main.c 加载在命令行上命名的插件文件,并执行其const char *register_plugin(void);
功能。由于函数名称在所有插件之间共享,因此我们需要在本地将它们链接(RTLD_LOCAL
)。
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <stdio.h>
static const char *load_plugin(const char *pathname)
{
const char *errmsg;
void *handle; /* We deliberately leak the handle */
const char * (*initfunc)(void);
if (!pathname || !*pathname)
return "No path specified";
dlerror();
handle = dlopen(pathname, RTLD_NOW | RTLD_LOCAL);
errmsg = dlerror();
if (errmsg)
return errmsg;
initfunc = dlsym(handle, "register_plugin");
errmsg = dlerror();
if (errmsg)
return errmsg;
return initfunc();
}
int main(int argc, char *argv[])
{
const char *errmsg;
int arg;
if (argc < 1 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s plugin [ plugin ... ]\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
for (arg = 1; arg < argc; arg++) {
errmsg = load_plugin(argv[arg]);
if (errmsg) {
fflush(stdout);
fprintf(stderr, "%s: %s.\n", argv[arg], errmsg);
return EXIT_FAILURE;
}
}
fflush(stdout);
fprintf(stderr, "All plugins loaded successfully.\n");
return EXIT_SUCCESS;
}
插件将通过在 plugin_system.h中 声明的某些函数(和/或变量)进行 访问 :
#ifndef PLUGIN_SYSTEM_H
#define PLUGIN_SYSTEM_H
extern void plugin_message(const char *);
#endif /* PLUGIN_SYSTEM_H */
它们在 plugin_system.c 中实现:
#include <stdio.h>
void plugin_message(const char *msg)
{
fputs(msg, stderr);
}
并在 plugin_system.list中 列为动态符号:
{
plugin_message;
};
我们还需要一个插件 plugin_foo.c :
#include <stdlib.h>
#include "plugin_system.h"
const char *register_plugin(void) __attribute__((used));
const char *register_plugin(void)
{
plugin_message("Plugin 'foo' is here.\n");
return NULL;
}
并且只是为了消除关于使每个插件具有相同名称的注册功能(另一个名为 plugin_bar.c)的 功能产生什么影响的困惑:
#include <stdlib.h>
#include "plugin_system.h"
const char *register_plugin(void) __attribute__((used));
const char *register_plugin(void)
{
plugin_message("Plugin 'bar' is here.\n");
return NULL;
}
为了使所有这些都易于编译,我们需要一个 Makefile :
CC := gcc
CFLAGS := -Wall -Wextra -O2
LDFLAGS := -ldl -Wl,-dynamic-list,plugin_system.list
PLUGIN_CFLAGS := $(CFLAGS)
PLUGIN_LDFLAGS := -fPIC
PLUGINS := plugin_foo.so plugin_bar.so
PROGS := example
.phony: all clean progs plugins
all: clean progs plugins
clean:
rm -f *.o $(PLUGINS) $(PROGS)
%.so: %.c
$(CC) $(PLUGIN_CFLAGS) $^ $(PLUGIN_LDFLAGS) -shared -Wl,-soname,$@ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $^
plugins: $(PLUGINS)
progs: $(PROGS)
example: main.o plugin_system.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
请注意,Makefile文件需要使用制表符(而不是空格)来指定;在此处列出文件始终会将它们转换为空格。因此,如果将以上内容粘贴到文件中,则需要通过以下方式修复缩进:
sed -e 's|^ *|\t|' -i Makefile
运行一次以上是安全的;最糟糕的是,搞砸了您的“人类可读”布局。
使用例如编译以上
make
并通过例如运行它
./example ./plugin_bar.so ./plugin_foo.so
将输出
Plugin 'bar' is here.
Plugin 'foo' is here.
All plugins loaded successfully.
到标准错误。
就个人而言,我更喜欢通过具有版本号和至少一个函数指针(指向初始化函数)的结构注册我的插件。这样,我可以在初始化之前加载所有插件,并解决例如插件之间的冲突或依赖关系。(换句话说,我使用具有固定名称的结构而不是具有固定名称的函数来标识插件。)
现在,至__attribute__((used))
。如果修改plugin_system.c
成
#include <stdio.h>
void plugin_message(const char *msg) __attribute__((used));
void plugin_message(const char *msg)
{
fputs(msg, stderr);
}
并将Makefile修改为LDFLAGS := -ldl
仅包含,示例程序和插件将编译得很好,但是运行它将产生
./plugin_bar.so: ./plugin_bar.so: undefined symbol: plugin_message.
换句话说,如果导出到插件的API是在单独的编译单元中编译的,则您将需要使用-rdynamic
或-Wl,-dynamic-list,plugin- system.list
确保功能已包含在最终可执行文件的动态符号表中;该used
属性不足。
如果要在最终二进制文件的动态符号表static
中plugin_system.o
包括所有非函数和符号,则可以例如将的结尾修改Makefile
为
example: main.o plugin_system.o
@rm -f plugin_system.list
./list_globals.sh plugin_system.o > plugin_system.list
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
与 list_globals.sh :
#!/bin/sh
[ $# -ge 1 ] || exit 0
export LANG=C LC_ALL=C
IFS=:
IFS="$(printf '\t ')"
printf '{\n'
readelf -s "$@" | while read Num Value Size Type Bind Vis Ndx Name Dummy ; do
[ -n "$Name" ] || continue
if [ "$Bind:$Type" = "GLOBAL:FUNC" ]; then
printf ' %s;\n' "$Name"
elif [ "$Bind:$Type:$Ndx" = "GLOBAL:OBJECT:COM" ]; then
printf ' %s;\n' "$Name"
fi
done
printf '};\n'
请记住,使脚本可执行chmod u+x list_globals.sh
。
https://marketplace.visualstudio.com/#VSCode Node.js插件支持 https://github.com/SamVerschueren/vscode-ava
YOG2 插件系统是整个框架的骨架。在 YOG2 中,从中间件管理到日志系统和FIS静态资源管理,所有功能的引入都是以插件的形式引入的,因此在了解每个功能的具体用法之前,我们需要对插件系统有一个整体的了解。 YOG2 插件系统的设计目标是 通过插件系统实现功能与配置的分离 功能由插件自身实现 配置由插件系统统一管理,完全暴露给用户 这样设计的优点是我们可以对 yog2 project 的运行时核心
在编写扩展之前,我们首先需要了解一下 Cocos Creator 3D 内,扩展的基础结构。 Cocos Creator 3D 编辑器是基于 GitHub 的 Electron 内核开发。Electron 是一个集成了 Node.js 和 Chromimu 的跨平台开发框架。 在 Electron 的架构中,一份应用程序由主进程和渲染进程组成,其主进程负责管理平台相关的调度,如窗口的开启关闭,菜单
问题内容: 我将很快开始一个新的C++项目(它可能也包含一些C组件),并且我正在寻找一个现代的,具有工业实力的(即非beta)构建系统。该软件将由3-5个开发人员在3-5年内创建,并将在Linux上运行(稍后 可能 会支持Mac OS X和Windows )。我正在寻找一种比例如具有更好的可理解性,易用性和可维护性,但仍足以处理复杂项目的功能。首选开源软件。 我开始寻找到,,和到目前为止,喜欢的功
本文向大家介绍Android系统实现DroidPlugin插件机制,包括了Android系统实现DroidPlugin插件机制的使用技巧和注意事项,需要的朋友参考一下 360手机助手使用的 DroidPlugin,它是360手机助手团队在Android系统上实现了一种插件机制。它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处。 它是一种新
const DI_ERROR_HANDLER = 'DI_ERROR_HANDLER'; const DI_LOGGER_WRITER = 'DI_LOGGER_WRITER'; const DI_SESSION_HANDLER = 'DI_SESSION_HANDLER'; const CONTROLLER_MAX_DEPTH = 'CONTROLLER_MAX_DEPT