问题贴:
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
分享到:
相关推荐
在电子设计大赛中,开发抽题小插件是一项常见的任务,用于随机选择参赛者需要解答的问题。本项目名为"C#抽题小插件",它采用C#编程语言实现,主要用于打开并处理PDF文件,这涉及到C#与PDF文档交互的技术。下面将详细...
首先,我们要明白手机端HTML5滑动选择题插件的主要目标是为用户提供一个流畅的、适应不同屏幕尺寸的选择题浏览和解答环境。这种插件通常具有响应式设计,能够自动适应手机、平板电脑等不同设备的屏幕大小。响应式...
例如,在多元函数微积分的学习中,自测题通过不同题型考察学生对知识点的掌握程度,包括了填空题、选择题、解答题和论述题。在这些题目中,学生不仅需要对多元函数的定义域、偏导数、方向导数、极值点、切平面方程、...
Vue.use是插件的使用方法,它接收一个插件作为参数并对其进行初始化,插件可以是一个对象或者一个安装函数。 综上所述,Vue.js面试题会覆盖这些核心知识点,通过回答这些问题,可以判断面试者是否具备Vue.js开发的...
1~31 题目 + 完整解答代码 1.分秒转换 2.字符串转换为整数 3.最大最小数字的差值 4.列表最后一个元素 5.比较字符串长度 6.字符串首尾连接 7.检查复数单词 8.列表唯一的数字 9.range转为列表 10.素数判断 11.元音...
《JavaWeb程序设计任务教程第2版》是一本深度探讨JavaWeb开发的教材,主要针对初学者和进阶者提供全面的知识覆盖。本教程通过详细解答课后习题,帮助读者深入理解JavaWeb编程的核心概念和技术。以下是相关知识点的...
首先,我们来详细分析第一个题目。 题目一的目标是编写一个汇编程序段,将BX寄存器中的内容以十六进制形式显示在屏幕上。这个程序段包含了一些关键步骤: 1. 保存现场,即保存可能被修改的寄存器(AX、CX、DX)。 ...
根据提供的文件信息,我们可以提炼出一系列有关BIM(Building Information Modeling,建筑信息模型)及Revit软件应用的知识点。以下是详细解析: 1. BIM技能等级考试:这是针对建筑行业专业技术人员的技能水平评价...
给定长度为 N的数列 A,以及 M条指令,每条指令可能是以下两种之一: 1 x y,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤y{∑i=lrA[i]}。 2 x y,把 A[x]改成 y。 对于每个查询指令,输出一个整数表示答案...
2. **可扩展性**:设计为模块化、插件化,允许添加自定义功能和集成第三方服务,以满足不同场景的需求。 3. **自动化**:Kubernetes能够自动部署、重启、复制和扩展容器,确保服务的高可用性和稳定性。 Kubernetes...
《C++ Primer Plus 第五版 中文版课后习题答案详解》是针对C++初学者及进阶者的一份宝贵资源,它详尽解答了该书中的所有练习题,帮助读者巩固和深化对C++语言的理解。这本书的第五版在原有的基础上进行了更新和改进...
标题和描述中提到的文件是"辽宁省大连市普兰店区第一中学2021届高三上学期第二阶段考试物理试题 Word版含答案.rar",这实际上是一个压缩文件,里面包含的是一个Word文档,该文档提供了针对高中三年级学生的一次物理...
【本人专注IT领域】:有任何使用问题欢迎随时与我联系,我会及时解答,第一时间为您提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【适合场景】:相关项目设计中...
2. **边界处理:**注意处理边界情况,例如第一个和最后一个传送阵。 3. **最优子结构:**利用最优子结构性质,从后向前计算每个传送阵所能达到的最大不同传送阵数目。 **知识点扩展:** - **动态规划原理与应用:**...
本篇将深入探讨一份针对前端开发者设置的自学和自测练习题,特别是其中的第三题代码。这份练习题集旨在帮助学习者巩固JavaScript基础,并逐步迈向更高阶的应用。 首先,我们来理解JavaScript的重要性。JavaScript是...
1. **第一题:合并有序数组** 这是一道关于数组操作的问题,目的是找到两个已排序的整数数组中的交集。在 C++、C#、Java 等编程语言中,我们可以使用双指针法来解决这个问题。首先,将两个数组合并成一个新的数组,...
通信原理习题 第六章 数字信号的调制传输共3页,第2页2、已知解调器输入端的峰值信噪比为 8 dB,分别计算 2ASK 和 2PSK 相干解调的误比特率,并进行
通信原理习题 第五章 数字信号的基带传输共 4页,第 2页2、已知某数字代码序列对应的 CMI 码基带信号波形如图所示,画出对应的 HDB3 码基带信号的波形,
"百度Android工程师面试题.zip" 这个标题表明这是一份与百度公司面试Android工程师相关的资料集合,通常包含常见的面试问题、技术要点以及可能的解答。这份资料可能涵盖了Android开发的基础到高级知识,旨在帮助应聘...
GIS,全称Geographic Information System,即地理信息系统,是一种集成了计算机硬件、软件以及地理数据的系统,用于捕捉、存储、分析、展示和管理与地理位置相关的所有类型的信息。在第9届全国大学生GIS技能大赛中,...