方汉
在Linux 上愈来愈多的应用程序利用Plugin编程技术来实现扩展功能,目前应用比较广泛的有Gimp、Netscape/Mozilla、XMMS和Nessus等,本文将简单介绍这些软件的Plugin的架构和编程。
给软件以生命力的Plugin
众所周知,要让一个软件具有生命力,最重要的一点是要让它提供并支持越来越多的功能,而这一点单凭开发者自身是很难独立做到的,需要让第三方的软件开发者可以通过作者提供的一种途径来为该软件添加功能。
最初,人们是使用关联模式,也就是像Windows下的文件扩展名一样,根据不同的文件关联,使用不同的软件打开相应的文件。这种方式编写扩展最为容易和自由,但是存在一定的缺陷,首先是不能利用宿主软件的资源,其次是软件的界面风格不易统一、灵活性不好。后来出现了Plugin结构,也就是利用动态连接库的方式制作Plugin(在Windows平台下是DLL、在Linux/Unix环境下是share object library),Plugin程序只能依附于宿主程序运行,不能独立运行,使用Plugin的优点是系统开销小、速度快,同宿主程序结合紧密、灵活性好。
在Linux上,最著名的Plugin架构就是Netscape提出的Plugin架构,比较出名的还有WinAmp(www.winamp.com)(在Linux 上改名叫XMMS,网址为www.xmms.org),这个MP3 播放软件中多彩多姿的插件(Plugin)系统和皮肤(skin)系统为其一统天下做出了不可磨灭的贡献。现在WinAmp已开始提供一个NSDN(Null Soft Develop Network)来让大家开发Plugin,由此可见Plugin对于一个软件来说是多么重要。
Linux下插件安装
在Linux上,使用某种软件的Plugin的方法很简单,一般该宿主软件都会提供一些缺省的Plugin,而且会搜寻相应的Plugin目录,用户可以把Plugin安装到系统的Plugin目录下也可以安装到自己的目录下。比如,要安装Netscape的Plugin,可以设定$NPX_PLUGIN_PATH这个环境变量到你的新Plugin目录,或者把Plugin安装到下列路径上:
/usr/local/lib/netscape/Plugins
$MOZILLA_HOME/Plugins
$HOME/.netscape/Plugins
Netscape会自动搜寻这些Plugin目录来自动加载相应的Plugin,其他的软件如Gimp、 XMMS和Nessus等的使用方法与此大同小异。
最简单Plugin架构的实现
首先,我们要编写一个宿主程序,该宿主程序必须为Plugin提供相应的接口,例如在本例子中的Action,然后宿主程序可以通过dlopen来打开相应的Plugin,调用dlsym 和createproc来创建Plugin的进程,最后还要调用Plugin执行相应的动作,一切完成后要用dlclose 来关闭Plugin。宿主程序的源代码如下:
Plugin_main.h
#ifndef _PLUG_MAIN_H_
#define _PLUG_MAIN_H_
// 定义强制C类型以回避C++命名规范,这样我们就可以定义dlsym()
#ifdef __cplusplus
extern ""C"" {
#endif
class CPlugMain
{
public:
virtual int Action() = 0;
};
// 下面是Plugin的通用函数,每个Plugin都会重载这个函数,主程序将创建惟一的子进程
extern CPlugMain CreatePlug();
typedef CPlugMain (CREATEPLUG_PROC)();
#ifdef __cplusplus
}
#endif //#define cplusplus
#endif //#define _PLUG_MAIN_H_
Plugin_main.cpp
#include ""Plugin_main.h""
#include <stdio.h>
#include <dlfcn.h> //动态链接库相关函数
//下面将定义固定的Plugin名称,读者可以自行定义如同Windows注册表那样的Plugin注册系统
#define NUM_PLUGINS 2
char szPlugins[] =
{
""./Plugin1.so"",
""./Plugin2.so""
};
int main( int argc, char argv )
{
CREATEPLUG_PROC createproc[NUM_PLUGINS];
CPlugMain pPlugins[NUM_PLUGINS];
void handle[NUM_PLUGINS];
char error;
int i;
// 加载所有Plugin
for ( i=0; i<NUM_PLUGINS; i++ )
{
printf( ""加载Plugin %s. "", szPlugins[i] );
// 加载Plugin动态链接库
if (NULL == (handle[i] = dlopen( szPlugins[i],RTLD_LAZY )))
{
handle[i] = NULL;
printf( ""dlopen error (%s)"", szPlugins[i] );
}
else
{
// 取得CreatePlug的地址
createproc[i] = (CREATEPLUG_PROC)dlsym(handle[i], ""CreatePlug"" );
if ((error = dlerror()) != NULL)
{
dlclose( handle[i] );
handle[i] = NULL;
printf( ""dlsym error (%s) "",szPlugins[i] );
}
else
{
// 创建Plugin进程
pPlugins[i] = createproc[i]();
}
}
}
// 执行Plugin的动作
printf( ""正在运行Plugin .. "" );
for ( i=0; i<NUM_PLUGINS; i++ )
{
if (handle[i] != NULL)
{
pPlugins[i]->Action();
}
}
// 关闭Plugin
for ( i=0; i<NUM_PLUGINS; i++ )
{
if (handle[i] != NULL)
{
dlclose( handle[i] );
handle[i] = NULL;
}
}
return 0;
}
下面是一个最简单的Plugin,只包含了Plugin_main.h 这个头文件,并且实现Action和CreatePlug这两个函数。源程序如下:
Plugin_1.h
#ifndef _PLUGIN_1_H_
#define _PLUGIN_1_H_
#include ″Plugin_main.h″
class CPlugin1 public CPlugMain
public
virtual int Action
#endif //#define _PLUGIN_1_H_
Plugin_1.cpp
#include ″Plugin_main.h″
#include ″Plugin1.h″
#include <stdio.h>
int CPlugin1Action
printf( ""这是Plugin 1,运行正常 "" );
return 0;
}
CPlugMain CreatePlug()
{
return new CPlugin1;
}
}
值得注意的是,编译这个Plugin的时候要使用 -shared参数来产生动态链接库.so文件,例如:gcc -o Plugin1.so Plugin1.o -shared。
Plugin开发实战
我们来看看如何为现有的一些软件编写Plugin,其中最复杂的是Netscape 4.x 和Mozilla (Netscape 6.x)的Plugin编写工作,而Xmms/Gimp/Nessus的Plugin相对来说要容易编写得多。
1. Netscape 4.x / Mozilla 的Plugin结构
Netscape的Plugin结构相对比较古老,Netscape提供的接口包括NPP 系列和NPN 系列,其中NPP 系列中Plugin必须自行实现,包括:NPP_Destroy、NPP_DestroyStream、
NPP_GetJavaClass、NPP_HandleEvent、NPP_Initialize、NPP_New、NPP_NewStream、NPP_Print、NPP_SetWindow、NPP_Shutdown、NPP_StreamAsFile、NPP_URLNotify、NPP_Write和NPP_WriteReady。
NPN 系列是Plugin要求Netscape提供的一些函数,包括:NPN_DestroyStream、
NPN_GetJavaEnv、NPN_GetJavaPeer、NPN_GetURL、
NPN_MemAlloc、NPN_MemFlush、NPN_MemFree、
NPN_NewStream、NPN_PostURL、NPN_RequestRead、
NPN_Status、NPN_UserAgent、NPN_Version和NPN_Write。
每种Plugin都有两种工作模式,即嵌入式和全页面方式,Plugin需要实现的工作包括以下内容:
(1)登记一种或几种Plugin要操作的MIME格式;
(2)在浏览器的窗口中间绘图;
(3)接收鼠标/键盘输入;
(4)从相应的URL中下载/发送数据。
用户如果要开发Netscape的Plugin,首先要在ftp://ftp.netscape.com/pub/sdk/Plugin/unix
下载相应的SDK 文件,里面有简单的例子,用户可以自行修改成自己的Plugin。
Mozilla/Netscape 6.x的Plugin架构是目前最先进的一种,它主要有下列优点:
(1)提供基于C++的API函数;
(2)提供了XPCOM,它是COM (the Component Object Model)的一个子集,XP的意思是cross-platform(跨平台),这使得新的Plugin的跨平台性和不同版本的兼容性得到了极大的提高;
(3)完全向后兼容,所有老的4.x 系列的Netscape Plugin 都可以继续使用。
Mozilla 的Plugin架构将原来的NPP 系列接口改变为:NPIPlugin、 NPIPluginInstance和NPIPluginStream 三个类;NPN 系列接口被扩充为NPIPluginManager、NPIPluginManagerStream、NPIPluginInstancePeer和NPIPluginStreamPeer
四个类,其中NP的意思是Netscape Plugin, I的意思是Interface。
linux插件开发参考文献 xmms插件开发指南: http://www.xmms.org
gimp插件开发指南: http://www.gimp.org/plugin_devel.html
nessus插件开发指南: http://www.nessus.org/doc/plugins_api.txt
http://www.nessus.org/doc/nasl.html
netscape 4.x 系列插件开发指南:
http://developer.netscape.com/docs/manuals/
communicator/plugin/index.htm
ftp://ftp.netscape.com/pub/sdk/plugin/unix
mozilla netscape 6.x 系列插件开发指南:
http://www.doczilla.com/development/extmoz.html
http://www.mozilla.org/docs/plugin.html
http://www.mozilla.org/docs/extendmoz.html
要开发Mozilla 的Plugin,用户需要下载Mozilla 的源代码,而Plugin的例子程序可以在http://lxr.mozilla.org/mozilla/source/
Plugin/ 上查阅,具体文档请参阅附录。
2. Xmms的Plugin架构
Xmms的Plugin分为输入(Input )、输出(Output)、可视化(Visualization)、通用(General)、效果(Effect)和其他(Misc)五种,要开发Xmms的Plugin需要安装xmms-devel这个软件包,下面是一个最简单的Xmms可视化的Plugin:
xmms-Plugin.c
#include <gtk/gtk.h>
#include ""xmms/Plugin.h""
static GtkWidget window = NULL,button;
static void Plugin_init(void);
static void Plugin_cleanup(void);
VisPlugin Plugin_vp =
{
NULL,
NULL,
0,
NULL, / 描述函数 /
0,
1,
Plugin_init, / 初始化函数 /
Plugin_cleanup, / 结束函数 /
NULL, / 关于函数 /
NULL, / 配置函数 /
NULL, / disable_Plugin /
NULL, / playback_start /
NULL, / playback_stop /
NULL, / render_pcm /
NULL / render_freq /
};
VisPlugin get_vPlugin_info(void)
{
Plugin_vp.description =
g_strdup_printf(""HelloWorld!"");
return &&Plugin_vp;
}
#define WIDTH 250
#define HEIGHT 100
static void Plugin_destroy_cb(GtkWidget w,gpointer data)
{
Plugin_vp.disable_Plugin(&&Plugin_vp);
}
static void Plugin_init(void)
{
if(window)
return;
window = gtk_window_new(GTK_WINDOW_DIALOG);
gtk_window_set_title(GTK_WINDOW(window),""Hello World"");
gtk_window_set_policy(GTK_WINDOW(window),FALSE, FALSE, FALSE);
gtk_widget_realize(window);
gtk_widget_set_usize(window, WIDTH, HEIGHT);
button = gtk_button_new_with_label(""Hello World"");
gtk_container_add (GTK_CONTAINER (window),button);
gtk_widget_show(button);
gtk_widget_show(window);
}
static void Plugin_cleanup(void)
if (window)
{
gtk_widget_destroywindow
}
}
typedef void ( GimpInitProc) (void);
typedef void ( GimpQuitProc) (void);
typedef void ( GimpQueryProc) (void);
typedef void ( GimpRunProc) (gchar name,
gint nparams,
GimpParam param,
gint nreturn_vals,
GimpParam return_vals);
struct _GimpPluginInfo
{
/ Plugin初始化函数 /
GimpInitProc init_proc;
/ Plugin推出函数 /
GimpQuitProc quit_proc;
/ 告知宿主程序本Plugin的功能,执行注册到PDB的功能 /
GimpQueryProc query_proc;
/ 实现Plugin的功能 /
GimpRunProc run_proc;
};