插件!我相信大家都不陌生吧,但是大家有没有想过插件是怎么做的呢?为什么程序已经打包发布了,但加入插件又可以动态增加新的功能。比如wirehark这款网络抓包软件,我们就可以编写插件,解析自定义的协议。
为自己的程序编写插件步骤:
编写主要的应用程序。
编写插件程序。
将插件打包成动态库。
将动态库拷贝到主要应用程序指定目录。
重新运行并加载动态库即可实现加载插件。
通过上述的步骤,其实已经说明了制作插件的关键点,即动态库。接下来我们一步一步地写程序实现插件机制,了解制作插件的过程。
第一步:先确定接口,即主程序预留给插件接口,其实就是开放的函数接口。
#ifndef __INTERFACE_H__
#define __INTERFACE_H__
typedef struct plugin_info_t
{
int id;
char name[32];
void (*show)(struct plugin_info_t *info); //指针函数,显示插件中的信息
void (*operation)(struct plugin_info_t *info);//插件操作指针函数
}plugin_info_t;
plugin_info_t* register_plugins_func(void); //注册插件地函数
#endif // !__INTERFACE_H__
第二步:编写主程序,即加载插件的。
#include "interface.h"
#include
#include
#include
#include
#include
#include
#define MAX_PLUGIN_NUM 10 //最大允许加载外部插件的个数,可修改
#define PLUGINS_PATH "./lib" //插件存放路径
plugin_info_t *ginfo[MAX_PLUGIN_NUM]; //指针数组,用于存储加载的插件信息
void* dlhandler[MAX_PLUGIN_NUM]; //存放加载动态库的句柄
typedef plugin_info_t* (*register_func)(void); //注册函数指针
/**
* @brief:遍历lib目录下的so文件,加载插件
* @input: NONE
* @output: OK:加载的插件个数 ERR:0
*/
int load_plugins(void)
{
DIR *dir=NULL;
struct dirent *ptr=NULL;
char base[128]={0};
const char *path=PLUGINS_PATH; //插件加载路径
int plugin_cnt=0; //记录以及加载的插件个数
if ((dir=opendir(path)) == NULL) {
perror("Open dir error...");
return 0;
}
while ((ptr=readdir(dir)) != NULL)
{
if(strcmp(ptr->d_name,".")==0 || strcmp(ptr->d_name,"..")==0) //当前路径或父路径
continue;
else if(ptr->d_type == 8){
sprintf(base, "%s/%s", path,ptr->d_name); //获取动态库名字
dlhandler[plugin_cnt]=dlopen(base, RTLD_LAZY); //打开动态库
if(!dlhandler[plugin_cnt]){
perror("Load so error!\n");
continue;
}
register_func rfunc = dlsym(dlhandler[plugin_cnt], "register_plugins_func"); //找到动态库中的函数符合
if(!rfunc){
perror("Load Plugin Error!");
continue;
}
ginfo[plugin_cnt] = rfunc(); //获取插件中的信息,存入结构体数组
++plugin_cnt;
if(plugin_cnt >= MAX_PLUGIN_NUM) return 0; //判断是否超出最大的插件个数
}
}
closedir(dir);
return plugin_cnt;
}
void release_plugin(int num)
{
int i=0;
for(i=0; i dlclose(dlhandler[i]); //关闭加载插件的句柄 } } int main(void) { int i=0; int plugin_num=0; plugin_num = load_plugins(); //加载插件 if(plugin_num==0){ printf("Load Plugins Error!!!\n"); return -1; } for(i=0; i ginfo[i]->show(ginfo[i]); //结构体数组保存插件的信息,调用结构体中的函数指针 ginfo[i]->operation(ginfo[i]); //调用结构体中的函数指针 printf("\n"); //多一空行,方便显示 } release_plugin(plugin_num); //释放资源 return 0; } 第三步:编写插件,因篇幅原因,只展示一个插件,其他两个插件与下面这个几乎一样的,我们也可以根据自己需求修改添加插件。 #include #include #include "interface.h" void demo1_show(struct plugin_info_t *info) //指针函数,显示插件中地信息 { printf("Demo1 Plugin Info: ID=%d Name=%s \n", info->id, info->name); } void demo1_operation(struct plugin_info_t *info) //插件操作指针函数 { //可自行发挥,写入想在插件中实现的功能 //此处演示,即打印一下信息 printf("Demo1 Plugin operation function...\n"); } plugin_info_t* register_plugins_func(void) //在插件中实现注册函数 { plugin_info_t* info = (plugin_info_t*)malloc(sizeof(plugin_info_t)); //申请内存 if(!info){ perror("Demo1 Plugin Error!\n"); return NULL; } info->id = 1; sprintf(info->name, "Demo1 Plugin"); info->show = demo1_show; //给指针函数赋值 info->operation = demo1_operation; //给指针函数赋值 printf("注册插件[demo1] 完成...\n"); return info; } 至此代码部分基本完成。PS:主程序中存储结构体信息采用的是数组,学有余力的小伙伴,可以尝试编写链表进行存储,使其更规范。 代码目录简介:此代码是Linux平台下运行的 plugin_code目录:包含所有的代码 app目录:存放的是主程序,即加载插件的。在此目录下还有一个lib目录,该目录是存放插件代码的动态库的,即so文件。 plugins目录:该目录存放插件代码,即plugin_demo1.c plugin_demo2.c、plugin_demo3.c三个插件。 编译运行: 编译主程序: 进入到plugin_code/app目录下,输入make命令,即可编译,生成的mainApp就是可执行文件。使用./mainApp运行。 编译插件:进入到plugin_code/plugin目录下,输入make命令编译插件。编译信息如下图: 运行程序效果:在plugin_code/app目录下输入 ./mainApp即可运行,下图输出的信息均来自3个插件输出的。 想要整个工程文件的小伙伴:请在微信公众号【Linux编程用C】回复 plugin 我是小C,欢迎大家关注、点赞支持,我们一起交流讨论学习!