方漢
在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.\n", szPlugins[i] );
// 載入Plugin動態鏈接庫
if (NULL == (handle[i] = dlopen( szPlugins[i], RTLD_LAZY )))
{
handle[i] = NULL;
printf( "dlopen error (%s)\n", 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)\n", szPlugins[i] );
}
else
{
// 創建Plugin進程
pPlugins[i] = createproc[i]();
}
}
}
// 執行Plugin的動作
printf( "正在運行Plugin ..\n" );
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 CPlugin1??Action??
?
printf( "這是Plugin 1,運行正常\n" );
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("Hello World!");
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_destroy?window??
}
}
3. Gimp的Plugin架構
Gimp的Plugin結構比較特殊,使用了宏定義來實現Plugin介面。
MAIN()宏調用gimp_main、使得Plugin可以被Gimp調用, Gimp 的Plugin介面比較簡單,在gimp啟動時將查詢所有的Plugin並註冊到一個PDB (procedural database)中,每個Plugin只需要實現下面的結構:
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;
};????????????????????????????????????????????????
Gimp的Plugin就是通過上面的四個函數來實現的。
關於Linux 下面的Plugin的使用和編程就介紹到這裡,其中Netscape/Mozilla的Plugin編程最為複雜,不過對有一定編程經驗的用戶來說,好好研究一下 Mozilla的Plugin編程是會很有好處的。
本文的所有例子程序都在Linux-Plugin-Example.zip中,用戶可以到http://opencjk.org/Linux-Plugin- Example.zip網址自行下載。 所有程序均在RedHat 7.0、gcc-2.96、 glibc-2.1.92 下編譯通過。