`
cloverprince
  • 浏览: 130189 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

插件问题回答第1题

阅读更多
问题贴: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#抽题小插件",它采用C#编程语言实现,主要用于打开并处理PDF文件,这涉及到C#与PDF文档交互的技术。下面将详细...

    手机端html5滑动选择题插件

    首先,我们要明白手机端HTML5滑动选择题插件的主要目标是为用户提供一个流畅的、适应不同屏幕尺寸的选择题浏览和解答环境。这种插件通常具有响应式设计,能够自动适应手机、平板电脑等不同设备的屏幕大小。响应式...

    第9章多元函数自测题1

    例如,在多元函数微积分的学习中,自测题通过不同题型考察学生对知识点的掌握程度,包括了填空题、选择题、解答题和论述题。在这些题目中,学生不仅需要对多元函数的定义域、偏导数、方向导数、极值点、切平面方程、...

    100道Vue.js 面试题(含答案).pdf

    Vue.use是插件的使用方法,它接收一个插件作为参数并对其进行初始化,插件可以是一个对象或者一个安装函数。 综上所述,Vue.js面试题会覆盖这些核心知识点,通过回答这些问题,可以判断面试者是否具备Vue.js开发的...

    PythonTip 题库:挑战练习-入门挑战 1~31 题目 + 完整解答代码

    1~31 题目 + 完整解答代码 1.分秒转换 2.字符串转换为整数 3.最大最小数字的差值 4.列表最后一个元素 5.比较字符串长度 6.字符串首尾连接 7.检查复数单词 8.列表唯一的数字 9.range转为列表 10.素数判断 11.元音...

    JavaWeb程序设计任务教程第2版课后题答案

    《JavaWeb程序设计任务教程第2版》是一本深度探讨JavaWeb开发的教材,主要针对初学者和进阶者提供全面的知识覆盖。本教程通过详细解答课后习题,帮助读者深入理解JavaWeb编程的核心概念和技术。以下是相关知识点的...

    课堂练习题(2题)参考解答1

    首先,我们来详细分析第一个题目。 题目一的目标是编写一个汇编程序段,将BX寄存器中的内容以十六进制形式显示在屏幕上。这个程序段包含了一些关键步骤: 1. 保存现场,即保存可能被修改的寄存器(AX、CX、DX)。 ...

    第十三期全国BIM技能等级考试一级试题.pdf

    根据提供的文件信息,我们可以提炼出一系列有关BIM(Building Information Modeling,建筑信息模型)及Revit软件应用的知识点。以下是详细解析: 1. BIM技能等级考试:这是针对建筑行业专业技术人员的技能水平评价...

    ACwing第245题 你能回答这些问题吗 代码

    给定长度为 N的数列 A,以及 M条指令,每条指令可能是以下两种之一: 1 x y,查询区间 [x,y] 中的最大连续子段和,即 maxx≤l≤r≤y{∑i=lrA[i]}。 2 x y,把 A[x]改成 y。 对于每个查询指令,输出一个整数表示答案...

    kubernetes(2021-12月整理k8s经典面试常问题目)面试题汇总.pdf

    2. **可扩展性**:设计为模块化、插件化,允许添加自定义功能和集成第三方服务,以满足不同场景的需求。 3. **自动化**:Kubernetes能够自动部署、重启、复制和扩展容器,确保服务的高可用性和稳定性。 Kubernetes...

    C++ Primer Plus 第五版 中文版课后习题答案详解

    《C++ Primer Plus 第五版 中文版课后习题答案详解》是针对C++初学者及进阶者的一份宝贵资源,它详尽解答了该书中的所有练习题,帮助读者巩固和深化对C++语言的理解。这本书的第五版在原有的基础上进行了更新和改进...

    辽宁省大连市普兰店区第一中学2021届高三上学期第二阶段考试物理试题 Word版含答案.rar

    标题和描述中提到的文件是"辽宁省大连市普兰店区第一中学2021届高三上学期第二阶段考试物理试题 Word版含答案.rar",这实际上是一个压缩文件,里面包含的是一个Word文档,该文档提供了针对高中三年级学生的一次物理...

    基于vue的做题插件,纯手写各种效果.zip

    【本人专注IT领域】:有任何使用问题欢迎随时与我联系,我会及时解答,第一时间为您提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【适合场景】:相关项目设计中...

    第十五届蓝桥杯大赛软件赛省赛第二场C/C++ 大学A B C组试题

    2. **边界处理:**注意处理边界情况,例如第一个和最后一个传送阵。 3. **最优子结构:**利用最优子结构性质,从后向前计算每个传送阵所能达到的最大不同传送阵数目。 **知识点扩展:** - **动态规划原理与应用:**...

    一些前端学习过程的自测练习题(JS练习第三题代码)

    本篇将深入探讨一份针对前端开发者设置的自学和自测练习题,特别是其中的第三题代码。这份练习题集旨在帮助学习者巩固JavaScript基础,并逐步迈向更高阶的应用。 首先,我们来理解JavaScript的重要性。JavaScript是...

    MultiVAC笔试题B1

    1. **第一题:合并有序数组** 这是一道关于数组操作的问题,目的是找到两个已排序的整数数组中的交集。在 C++、C#、Java 等编程语言中,我们可以使用双指针法来解决这个问题。首先,将两个数组合并成一个新的数组,...

    习题5参考解答1

    通信原理习题 第六章 数字信号的调制传输共3页,第2页2、已知解调器输入端的峰值信噪比为 8 dB,分别计算 2ASK 和 2PSK 相干解调的误比特率,并进行

    习题4参考解答1

    通信原理习题 第五章 数字信号的基带传输共 4页,第 2页2、已知某数字代码序列对应的 CMI 码基带信号波形如图所示,画出对应的 HDB3 码基带信号的波形,

    百度Android工程师面试题.zip

    "百度Android工程师面试题.zip" 这个标题表明这是一份与百度公司面试Android工程师相关的资料集合,通常包含常见的面试问题、技术要点以及可能的解答。这份资料可能涵盖了Android开发的基础到高级知识,旨在帮助应聘...

    【GIS学习资料】第9届全国大学生 GIS 技能大赛下午试题详解

    GIS,全称Geographic Information System,即地理信息系统,是一种集成了计算机硬件、软件以及地理数据的系统,用于捕捉、存储、分析、展示和管理与地理位置相关的所有类型的信息。在第9届全国大学生GIS技能大赛中,...

Global site tag (gtag.js) - Google Analytics