插件问题回答第1题

问题贴:[url]http://cloverprince.iteye.com/blog/481307[/url]

[quote]1. 现有一个主程序用C语言写成。现在要允许第三方开发人员编写扩展的模块,约定第三方开发的模块必须提供一系列已知名称的函数(如 foo(),bar(),baz())。如果要求第三方的模块必须与主程序的二进制代码分开发布,把dll或so丢在某个文件夹内即可被动态装载并使用,应如何实现?
[/quote]


回答:

操作系统提供了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 */


总体的目录结构如下:
[quote].
|-- Makefile
|-- main
|-- main.c
|-- plugin-interface.h
`-- plugins
|-- Makefile
|-- goodbyeworld.c
|-- goodbyeworld.o
|-- goodbyeworld.so
|-- helloworld.c
|-- helloworld.o
`-- helloworld.so[/quote]

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,这对动态链接(静态装载)有用,但是不加这个选项仍然可以动态装载。

执行时,需要的最少的目录结构如下:
[quote].
|-- main
`-- plugins
|-- goodbyeworld.so
`-- helloworld.so
[/quote]

执行:
[quote][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
[/quote]

总结:
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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值