BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / soft-design / #35842同步于 2009/10/1
该镜像源已超过 30 天没有更新,可能在源站已被删除。
SoftDesign机器人发帖

[脑残的回答]问题1

wks
2009/10/1镜像同步0 回复
1. 现有一个主程序用C语言写成。现在要允许第三方开发人员编写扩展的模块,约定第三方开发的模块必须提供一系列已知名称的函数(如 foo(),bar(),baz())。如果要求第三方的模块必须与主程序的二进制代码分开发布,把dll或so丢在某个文件夹内即可被动态装载并使用,应如何实现? 回答: 操作系统提供了shared object的动态装载功能。定义在dlfcn.h中。调用dlopen()打开插件,dlsym()获取函数,dlclose()关闭插件。 适用于: *nix。在Linux中实验成功。 实现: 我们假定每个插件提供两个函数: void hello(void); // 显示Hello world void greet(char* name); // 给你打招呼 为了简化装载,我们用一个struct储存以上两个函数的指针,并要求每个插件内提供一个叫init_module的函数,填充该struct。 接口头文件如下: /* plugin-interface.h */ #ifndef _PLUGIN_INTERFACE_H_ #define _PLUGIN_INTERFACE_H_ #ifdef __cplusplus extern "C" { #endif typedef struct _PluginInterface { // 这个结构储存了插件需要提供的所有函数的指针 void (*hello)(void); void (*greet)(char* name); } PluginInterface; typedef void (*InitModuleFunc)(PluginInterface* iface); // 这个函数填充上述结构。 #ifdef __cplusplus } #endif #endif /* end of plugin-interface.h */ 总体的目录结构如下: . |-- Makefile |-- main |-- main.c |-- plugin-interface.h `-- plugins |-- Makefile |-- goodbyeworld.c |-- goodbyeworld.o |-- goodbyeworld.so |-- helloworld.c |-- helloworld.o `-- helloworld.so plugins里的.so将被主程序main装载。 看看plugins/helloworld.c: /* plugins/helloworld.c */ #include <stdio.h> #include "../plugin-interface.h" // 两个功能函数hello, greet的名称随便。我们只关心它们的指针。 void hw_hello() { printf("Hello world!\n"); } void hw_greet(char* name) { printf("Hello, %s\n",name); } // 填充PluginInterface结构。 void init_module(PluginInterface *iface) { iface->hello = hw_hello; iface->greet = hw_greet; } /* end of plugins/helloworld.c */ plugins/goodbyeworld.c是另一个插件 /* plugins/goodbyeworld.c */ #include <stdio.h> #include "../plugin-interface.h" void gw_hello() { printf("Goodbye world!\n"); } void gw_greet(char* name) { printf("Goodbye, %s\n",name); } void init_module(PluginInterface *iface) { iface->hello = gw_hello; iface->greet = gw_greet; } /* end of plugins/goodbyeworld.c */ 最后,main.c是主程序。 /* main.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> // dlopen(), dlsym(), dlclose() #include <sys/types.h> #include <dirent.h> #include "plugin-interface.h" #define MAX_PLUGINS 10 char PLUGINS_PATH[] = "plugins"; struct { char path[256]; // 插件文件所在路径(仅供显示用) void* lib_handle; // 库句柄 PluginInterface iface; // 接口结构,在plugin-interface.h中定义 } plugins[MAX_PLUGINS]; // 每个struct对应一个插件 int n_plugins; void load_plugin(char* path) { void* lib_handle; InitModuleFunc init_func; char* err; lib_handle = dlopen(path, RTLD_LAZY); // 打开库 err = dlerror(); if(lib_handle==NULL) { fprintf(stderr,"Cannot open %s: %s\n",path,err); return; } init_func = dlsym(lib_handle, "init_module"); // 找到init_module函数。 err = dlerror(); if(err != NULL) { fprintf(stderr,"Cannot find function 'init_module' in %s: %s\n",path,err); dlclose(lib_handle); return; } strcpy(plugins[n_plugins].path,path); plugins[n_plugins].lib_handle=lib_handle; init_func(&plugins[n_plugins].iface); // 利用插件中的"init_module"函数,填充iface结构 n_plugins++; fprintf(stderr,"Plugin successfully loaded: %s\n",path); } int main() { DIR *dir; struct dirent *dent; int i; // 读取目录,装载所有的库 dir = opendir(PLUGINS_PATH); if(dir==NULL) { perror("opendir"); exit(1); } while((dent=readdir(dir))!=NULL) { int name_len; char plugin_path[256]; name_len = strlen(dent->d_name); if(name_len<3) continue; if(strcmp(dent->d_name+name_len-3,".so")!=0) continue; // 找到所有.so的文件 sprintf(plugin_path,"%s/%s",PLUGINS_PATH,dent->d_name); load_plugin(plugin_path); // 尝试装载这个.so的文件 } closedir(dir); // 测试每个插件 for(i=0;i<n_plugins;i++) { fprintf(stderr, "Testing %s ...\n",plugins[i].path); plugins[i].iface.hello(); plugins[i].iface.greet("wks"); } // 卸载 for(i=0;i<n_plugins;i++) { dlclose(plugins[i].lib_handle); } return 0; } /* end of main.c */ 编译: # Makefile all: main main: main.c plugin-interface.h gcc -rdynamic -ldl -o $@ $^ # end of Makefile main.c的dlopen()等函数需要-ldl选项。-rdynamic选项也是dlopen()等函数需要的。 # plugins/Makefile all: helloworld.so goodbyeworld.so helloworld.so: helloworld.c gcc -c -fPIC helloworld.c gcc -shared -o helloworld.so helloworld.o goodbyeworld.so: goodbyeworld.c gcc -c -fPIC goodbyeworld.c gcc -shared -o goodbyeworld.so goodbyeworld.o # end of plugins/Makefile 这些是插件的编译方法。-fPIC是构造so的必要条件。 另一个选项是-Wl,-soname,xxxxxxx.so.x,这对动态链接(静态装载)有用,但是不加这个选项仍然可以动态装载。 执行时,需要的最少的目录结构如下: . |-- main `-- plugins |-- goodbyeworld.so `-- helloworld.so 执行: [wks@localhost out]$ ./main Plugin successfully loaded: plugins/goodbyeworld.so Plugin successfully loaded: plugins/helloworld.so Testing plugins/goodbyeworld.so ... Goodbye world! Goodbye, wks Testing plugins/helloworld.so ... Hello world! Hello, wks 总结: 1. main程序并不了解plugins目录中有多少插件。在运行时列举目录。 2. main程序对每个plugins文件(比如叫helloworld.so)的了解只有: - helloworld.so中有一个函数叫init_module,可以填充PluginInterface结构。 - helloworld.so将实现hello和greet两个函数,但函数名可以不知道。函数指针被init_module提供。 - 用PluginInterface结构中的函数操作插件。 参考: http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
订阅后,新回复会通过你的通知中心匿名送达。
0 条回复
暂无回复 · 你可以订阅本帖等待新回复。