问题贴:
http://cloverprince.iteye.com/blog/481307
引用
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