支持插件的程序设计

来源:互联网 发布:无线通信d2d算法工程师 编辑:程序博客网 时间:2024/06/05 16:04

转载的地址:http://blog.csdn.net/jimmy54/article/details/5367882


支持插件的程序设计  

继续转一篇以前的帖子 01-23-2007

 

支持插件的程序设计

作者:dorainm

看到很多软件,尤其老外的,大型软件,异常支持插件功能
主程序简洁了不说,灵活性,
而且貌似老外闲人很多,帮忙编写支持其它功能的新插件
逐渐,该软件的功能就变得完善异常


插件有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.h*/
#ifndef __D_PLUG_H

#define __D_PLUG_H


#ifdef SHARED
        int ( *d_plug )( int x, int y, int *res );
#else
        int d_plug( int x, int y, int *res );
#endif

#endif



这个头文件说明,插件应该据有一个 d_plug函数,它的参数是这样这样的
然后在说明文档中着重解释一下主程序怎么使用它:
比如,主程序提供x,y两个数字,让插件来运算,返回给res

任何插件的编写者都明白来!!
那作为一员,我们现在编写自己的第一个插件


/*d_add.c*/
#include <stdio.h>
#include "d_plug.h"

int d_plug( int x, int y, int *res )
{
        *res = x+y;
        return 0;
};




够简单,而且让读者觉得简单得莫名其妙!
我们把 x和 y 相加后,交给了要作为返回的 res

如果觉得有些迷茫,我们看看主程序



/*main.c*/
#include <stdio.h>
/*LINUX下使用动态链接库,源程序需要包含dlfcn.h头文件,此文件定义了调用动态链接库的函数的原型*/
#include <dlfcn.h>

#define SHARED

/*插件的头文件*/
#include "d_plug.h"


/*显示程序信息*/
void disp_logo( void )
{
        printf(" a plug-in simple, by dorainm, dorainm@gmail.com/n");
        return;
};

/*显示语法信息*/
void disp_usage( char *app_name )
{
        printf( "usage : %s [plug-in file name (*.so) ]/n", app_name );
        return;
};



int main( int argc, char *argv[] )
{
        int res; /*用于装运算返回值*/
        int x=5; /*第一个运算数,这里固定为5*/
        int y=3; /*第二个运算数,当然我们可以让它们也用实参输入*/

        char *so_filename; /*插件的文件名*/

        void *dp; /*调用.so的句柄*/
        char *error; /*错误信息*/

        disp_logo();
        if( argc<2 )
        {
                disp_usage( argv[0] );
                return 1;
        }

        /*在实参中获取插件文件名*/
        so_filename = argv[1];
        printf("use shared object file : [ %s ]/n", so_filename );

        /*装载该插件,动态链接库*/
        dp = dlopen( so_filename, RTLD_LAZY );
        /*返回为NULL,则装载失败*/
        if ( dp==NULL )
        {
                fprintf( stderr, dlerror() );
                return 2;
        }

        printf( "load plug [ %s ] successfully./n", so_filename );


        /*获取动态链接库中的 d_plug 函数地址,即获取插件的功能代码*/
        d_plug = dlsym( dp, "d_plug" );
        error = dlerror();
        /*判断是否出错,比如该“不明插件”没有这函数*/
        if ( error )
        {
                fprintf( stderr, error );
                return 3;
        }


        /*执行插件的功能*/
        d_plug( x, y, &res );
        /*显示结果*/
        printf(" the result to %d, %d : %d./n", x, y, res );

        /*卸载插件*/
        dlclose( dp );

        return 0;
};




我们可以看到,主程序通过第一个实参,装载该动态链接库(插件)
获取插件中的 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]$

看,主程序根据第一个插件,已经成功实现来加法运算!

信心!!

再接再厉,我们来编写第二个插件: 实现减法
其实很简单,修改加法插件源码中的一个字符就可以

/*d_sub.c*/
#include <stdio.h>
#include "d_plug.h"

int d_plug( int x, int y, int *res )
{
        *res = x-y;
        return 0;
};


我们编译这个插件,试让主程序来接纳这个新伙伴

[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插件

主程序运行起来时,需要建立一张插件的表,存储插件列表
再读取相应的信息,给用户显示:
看,当前有这些这些插件
当用户选择其一,进行操作时,软件就调入该动态链接库
圆满完成用户预先想要的功能

这就是插件的奥秘!

原创粉丝点击