支持插件的程序设计
看到很多软件,尤其老外的,大型软件,异常支持插件功能
主程序简洁了不说,灵活性,
而且貌似老外闲人很多,帮忙编写支持其它功能的新插件
逐渐,该软件的功能就变得完善异常
插件有2种存在方式:
1、脚本
一个插件,就是一段脚本,主程序读取某个脚本,需要解释它,
然后再有选择性、逻辑性地执行早已编译主程序内的相对应的二进制代码!
脚本不许要系统的任何支持(系统:哼,我不给你 I/O 操作),也跟系统环境没有关系
编写这种脚本只需要一个文本编辑器,例如 vim 或 emacs 就可以了
但执行效率会稍稍逊色,而且脚本的设计代码容易暴露
针对陌生的脚本插件,如果主程序拥有足够的容错能力,还是能够避免程序崩溃
(dorainm:设计个逻辑无限循环的恶意插件,让主程序瞎耗去吧)
2、二进制
这种脚本,是一种链接库,由各种函数的已经编译好的二进制字符组成
而且必须要求操作系统能够支持一个程序调用链接库的功能
(类似win32的dll动态链接库,*nix的.o/.so共享对象)
还需要针对不同操作系统,软、硬件环境编译出相应的二进制代码,
当然,编写插件的时候,需要额外的编译器,如 gcc
这种插件执行效率相对比较高,源码相对也比较不透明
因为它本身就是已经被编译了的可执行二进制代码
但是一旦遇到恶意或者劣质的插件,主程序就会崩溃!
下面我们来介绍第二种插件的实现思想与方法,
一则,学会了第二种,第一种方法无非是编译器的工作变成了主程序的任务
二来,我们可以学习动态链接库的程序写法
它即能减少程序的大小,也能提高程序编译过程中的时间花费,
更能提高程序灵活性,比如升级、补丁;
不可能我们的软件只有一个可执行的二进制文件,
(比如CS,就一个 1.3G 大的 .exe,
升级方面不说,如果作者觉得 AWP 的甩枪需要修正,
修改源码,让电脑编译,我 阿富汗 旅游!!
旅游回来,汗,修改源码的时候,不小心多加了个逗号,
重新修正,再编译,得,这回去 伊拉克 旅游...)
关于插件的思想,是dorainm自己想的
不知道与当前各大软件的设计是否相似,如果有冗余或错误的地方,
还望斧正
环境:
系统 linux 2.6.15
编译器 gcc 4.1.0
文本编辑 emacs 21.4.1
关于动态链接库的介绍,引用 雨亦奇 的 <LINUX系统中动态链接库的创建与使用>
大家都知道,在WINDOWS系统中有很多的动态链接库(以.DLL为后缀的文件,DLL即Dynamic Link Library)。这种动态链接库,和静态函数库不同,它里面的函数并不是执行程序本身的一部分,而是根据执行程 ??要按需装入,同时其执行代码可在多个执行程序间共享,节省了空间,提高了效率,具备很高的灵活性,得到越来越多程序员和用户的青睐。那么,在LINUX系统中有无这样的函数 库呢? 答案是肯定的,LINUX的动态链接库不仅有,而且为数不少。在/lib目录下,就有许多以.so作后缀的文件,这就是LINUX系统应用的动态链接库,只不过与WINDOWS叫法不同,它叫so,即Shared Object,共享对 ??。(在LINUX下,静态函数库是以.a作后缀的) X-WINDOW作为LINUX下的标准图形窗口界面 ,它本身就采用了很多的动态链接库(在/usr/X11R6/lib目录下),以方便程序间的共享,节省占用空间。著名的APACHE网 ?服务器,也采用了动态链接库,以便扩充程序功能。你只需将PHP动态链接库拷到其共享目录,修改一下配置,APACHE就可以支持PHP网页了。如果你愿意,可以自己编写动态链接库 ,让APACHE支持你 己定义的网页格式。这就是动态链接的好处。 |
我们是做插件的!!
现在我们明白,主程序是一个可执行的二进制,但是它并不是完整的
它的某些部分与功能,需要插件来补充!!
对于插件来说,它们实现各自特殊的功能,
但是它们的设计,要针对主程序的要求,给主程序调用,提供统一意义的函数与接口
(比如主程序读取某个插件名字可能调用 get_plug_name 函数,
读取作者,可能调用 get_plug_author,
每个插件必须拥有这些函数,做相同意义的事情,
get_plug_author就提供作者,而不是插件的名字)
现在,我们这里提供一个最最简单的插件头文件,
任何插件编写者,可以根据这个头文件,填充自己的功能,实现自己的插件
|
这个头文件说明,插件应该据有一个 d_plug函数,它的参数是这样这样的
然后在说明文档中着重解释一下主程序怎么使用它:
比如,主程序提供x,y两个数字,让插件来运算,返回给res
任何插件的编写者都明白来!!
那作为一员,我们现在编写自己的第一个插件
|
够简单,而且让读者觉得简单得莫名其妙!
我们把 x和 y 相加后,交给了要作为返回的 res
如果觉得有些迷茫,我们看看主程序
|
我们可以看到,主程序通过第一个实参,装载该动态链接库(插件)
获取插件中的 d_plug 函数,然后根据d_plug头文件里面的规定
把两个运算数和保存结果值的地址 交与 d_plug 函数
然后显示结果!!
我们来编译主函数
[dorainm@localhost plug_simple]$ ls d_add.c d_plug.h main.c [dorainm@localhost plug_simple]$ cc -rdynamic -s -o simple main.c -ldl [dorainm@localhost plug_simple]$ ls d_add.c d_plug.h main.c simple [dorainm@localhost plug_simple]$ |
编译时要采用-rdynamic选项与-ldl选项
以产生可调用动态链接库(插件)的执行代码
现在,我们编译我们可爱的第一个插件
[dorainm@localhost plug_simple]$ ls d_add.c d_plug.h main.c simple [dorainm@localhost plug_simple]$ cc -shared -o d_add.so d_add.c [dorainm@localhost plug_simple]$ ls d_add.c d_add.so d_plug.h main.c simple [dorainm@localhost plug_simple]$ |
编译插件源程序时选用-shared选项即可创建动态链接库
我们现在来运行我们的主程序
[dorainm@localhost plug_simple]$ ./simple a plug-in simple, by dorainm, dorainm@gmail.com usage : ./simple [plug-in file name (*.so) ] [dorainm@localhost plug_simple]$ |
我们根据语法,加入我们第一个参数:我们的 d_add.so 插件
[dorainm@localhost plug_simple]$ ./simple ./d_add.so a plug-in simple, by dorainm, dorainm@gmail.com use shared object file : [ ./d_add.so ] load plug [ ./d_add.so ] successfully. the result to 5, 3 : 8. [dorainm@localhost plug_simple]$ |
看,主程序根据第一个插件,已经成功实现来加法运算!
信心!!
再接再厉,我们来编写第二个插件: 实现减法
其实很简单,修改加法插件源码中的一个字符就可以
|
我们编译这个插件,试让主程序来接纳这个新伙伴
[dorainm@localhost plug_simple]$ ls d_add.c d_add.so d_plug.h d_sub.c main.c simple [dorainm@localhost plug_simple]$ cc --shared -o d_sub.so d_sub.c [dorainm@localhost plug_simple]$ ls d_add.c d_add.so d_plug.h d_sub.c d_sub.so main.c simple [dorainm@localhost plug_simple]$ ./simple ./d_sub.so a plug-in simple, by dorainm, dorainm@gmail.com use shared object file : [ ./d_sub.so ] load plug [ ./d_sub.so ] successfully. the result to 5, 3 : 2. [dorainm@localhost plug_simple]$ |
成功实现来第二个插件提供的功能,减法!
乘法、除法插件的源码,想必就不需要提供来,也是修改一个字符!
下面看它们实现的效果
[dorainm@localhost plug_simple]$ ls d_add.c d_div.c d_plug.h d_sub.so simple d_add.so d_mul.c d_sub.c main.c [dorainm@localhost plug_simple]$ cc --shared -o d_mul.so d_mul.c [dorainm@localhost plug_simple]$ cc --shared -o d_div.so d_div.c [dorainm@localhost plug_simple]$ ls d_add.c d_div.c d_mul.c d_plug.h d_sub.so simple d_add.so d_div.so d_mul.so d_sub.c main.c [dorainm@localhost plug_simple]$ ./simple ./d_mul.so a plug-in simple, by dorainm, dorainm@gmail.com use shared object file : [ ./d_mul.so ] load plug [ ./d_mul.so ] successfully. the result to 5, 3 : 15. [dorainm@localhost plug_simple]$ ./simple ./d_div.so a plug-in simple, by dorainm, dorainm@gmail.com use shared object file : [ ./d_div.so ] load plug [ ./d_div.so ] successfully. the result to 5, 3 : 1. [dorainm@localhost plug_simple]$ |
乘法、除法插件也实现来!
当然,我们还可以编写,
比如把 x当作成绩,y当作及格标准,res返回该学生成绩是否合格的判断成绩的插件
这样,一个主程序,不修改自己任何代码,就可以实现了各种各样的功能:)
真实的软件
各种插件可能由一些插件列表维护,
比如一个插件配置的文本文件 plug.conf,里面有插件对应的位置列表
或者把插件都放置在某个文件夹中,
比如plug-in文件夹里,就丢着为主程序提供各种功能的.so插件
主程序运行起来时,需要建立一张插件的表,存储插件列表
再读取相应的信息,给用户显示:
看,当前有这些这些插件
当用户选择其一,进行操作时,软件就调入该动态链接库
圆满完成用户预先想要的功能
这就是插件的奥秘!
|