很多朋友都知道,在Linux操作系統中可以用C語言來編寫代碼,經過簡單的編譯,就可以輕鬆的開發出靜態鏈接庫或動態鏈接庫。 一般情況下我們使用動態鏈接庫,必需在編譯過程中加入相應的標識,使編譯出來的應用程序在運行時自動加載動態鏈接庫;而插件(plug-in)實際上就是 對動態鏈接庫的一種靈活運用,應用程序繞開了編譯過程中的鏈接這一操作環節,通過編碼直接調用動態庫中的函數和資源。

本文即以實例的形式向讀者介紹如何在Linux中應用GTK+工具中的GModule功能,在應用程序中實現插件。

插件的用途

動 態鏈接庫是Linux系統核心中的重要組成部分,插件則可以將應用程序的不同的功能放在動態鏈接庫中進行單獨的管理;還有很關鍵的一點,插件不象動態鏈接 庫那樣,由操作系統來統一裝載和卸載;而是由應用程序自己來裝載,這樣就減輕了系統資源的佔用,也增強了應用程序的靈活性。



GTK+中的插件編程

GTK +的底層基礎GLib中提供了插件編程功能,稱為GMoudle。它為實現插件功能提供了快捷的方法,在目前的GTK+2.0中,GMoudle比較完整 的封裝了Linux底層中對動態鏈接庫的操作dlopen,HP-UX系統中的shl_load和Windows平台上的有關DLL調用等功能,用非常簡 單的方法實現了動態加載插件和調用插件中的函數的功能。

如果你編寫的應用程序要用到GModule插件功能,就必須在編譯時加入 `pkg-config –-libs gmoudle-2.0`以完成最後程序運行的需要。




Gmoudle結構

GModule結構是編程中的關鍵,它是在gmodule.h文件中定義的,它是一個不透明(即對外編程不公開的)的結構,當我們打開一個插件時,返回的就是GModule型的指針,多數與插件相關的函數都用到這個指針。



平台支持

函數g_module_support用來測試當前程序運行平台是否支持插件調用,如果其返回結果為TRUE,則表明支持;返回FALSE則表明不支持。

打開和關閉

函 數g_module_open來打開指定的插件,它需要兩個參數,第一是要打開的插件文件名,第二是打開的方式,可以是0或 G_MODULE_BIND_LAZY,G_MODULE_BIND_LAZY表示只有當需 要時才將符號和插件綁定,而默認是插件打開時綁定所有的符號表,這裏的符號指的是插件中的函數或資源。

函數g_module_close來關閉打開的插件,此函數的參數是已打開的GModule型的指針,如果關閉成功,返回gboolean型值TRUE。

如果打開插件後不想再關閉的話,可以用函數g_module_make_resident來使它不能卸載,直到進程運行結束。它的參數也是打開的GModule型的指針,如此函數執行後,再執行g_module_close將會被忽略。

插件的位置

用 函數g_module_build_path來確定插件的位置,它有兩個參數,一個是目錄名,一個插件文件名,返回值是標準的插件位置,如在linux下 的/lib/mylibary.so或windows下的//windows//mylibary.dll。其中目錄名為NULL或空字符串,則設定。注 意事項不用時要釋放返回值的內存,使用標準的目錄前綴和文件名後綴。

插件的出錯信息

函數g_module_error顯示插件的最後出錯信息,在編程中可以用它來顯示出錯信息以便於調試和使用。如當用g_module_open函數打開插件時,返回值為NULL的話,説明打開失敗,這時g_module_error返回的出錯信息即為失敗的原因。




默認的執行函數

在 一個完整的插件中應該包括下面兩個函數:g_module_check_init和g_module_unload。其中函數 g_module_check_init在插件調入後默認執行,函數g_module_unload在插件卸載後默認執行。這兩個函數非常有用,我們這裏 只是用來顯示加載成功和卸載功能的消息,你完全可以用它來做一些預處理操作,如內存分配和釋放等。


取得插件文件名

函數g_module_name來取得插件的文件名,它的參數也是打開的GModule型指針,返回插件的文件名,如libhello.so。


插件的代碼

下 面的代碼就是一個簡單的GTK+插件,它主要功能是提供了兩個實用函數about和create_message_dialog,用來創建一個簡單的關於 對話框和消息對話框。同時我們還簡單的聲明瞭打開插件時自動調用的函數g_module_check_init和關閉插件時自動調用的函數 g_module_unload,以便在打開和關閉插件時顯示相對應的信息。

#include <gtk/gtk.h>
#include <gtk/gtk.h>
#include <gmodule.h>
#include "about.h"
//此函數在插件初始化時默認執行
void	g_module_check_init(GModule *module)
{
	g_print("插件%s初始化成功,已經調入!//n", g_module_name(module));
}
//以下函數當插件缷載時默認執行
void	g_module_unload(GModule *module)
{
	g_print("插件%s已成功缷載!//n", g_module_name(module));
}
//創建普通的彈出式消息框
void	create_message_dialog(gchar* message)
{
	GtkWidget *dialog;
	dialog = gtk_message_dialog_new(NULL,
			GTK_DIALOG_DESTROY_WITH_PARENT,
			GTK_MESSAGE_INFO ,
			GTK_BUTTONS_OK,
			message);
	gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
}
//以下內容為創建關於對話框
static GtkWidget *window;
void	on_ok_clicked(GtkWidget* widget, gpointer data)
{
	gtk_widget_destroy(data);
}
void	about(void)
{
	GtkWidget *vbox, *hbox;
	GtkWidget *label, *button, *image, *sep ;
	GdkPixbuf *pix;
	pix = gdk_pixbuf_new_from_inline(9216+24, my_pixbuf, TRUE, NULL);
	//此函數的第一個參數為圖像文件的長度,可以在about.h文件的頭部中找到
	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title(GTK_WINDOW(window),"關於");
	g_signal_connect(G_OBJECT(window),"delete_event",
			G_CALLBACK(gtk_widget_destroy),window);
	gtk_container_set_border_width(GTK_CONTAINER(window),10);
	vbox = gtk_vbox_new(FALSE,0);
	gtk_container_add(GTK_CONTAINER(window),vbox);
	hbox = gtk_hbox_new(FALSE,0);
	gtk_box_pack_start(GTK_BOX(vbox),hbox,FALSE,FALSE,5);
	image = gtk_image_new_from_pixbuf(pix);
	gtk_box_pack_start(GTK_BOX(hbox),image,FALSE,FALSE,5);
	label = gtk_label_new("此軟件用於測試");
	gtk_box_pack_start(GTK_BOX(hbox),label,FALSE,FALSE,5);
	sep = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX(vbox),sep,FALSE,FALSE,5);
	button = gtk_button_new_from_stock(GTK_STOCK_OK);
	g_signal_connect(G_OBJECT(button),"clicked",
			G_CALLBACK(on_ok_clicked),(gpointer)window);
	gtk_box_pack_start(GTK_BOX(vbox),button,FALSE,FALSE,0);
	gtk_widget_show_all(window);
}



插件的頭文件

為了讓其它使用者可以編程調用此插件,還要為上面的插件代碼建立一個簡單的頭文件,內容如下:

#ifndef __HELLO_H__
#define __HELLO_H__
void	g_module_check_init(GModule *module);
void	g_module_unload(GModule *module);
void	create_message_dialog(gchar* message);
void	about(void);
#endif




處理圖像

許 多在WINDOWS下開發過GUI應用程序的朋友都知道可以將一些資源如圖片、圖標等放到動態鏈接庫中,從而避免被其它程序刪除或更改的危險,在 Linux中也可以,hello.c這段代碼中的#include "about.h"就將一幅圖像保存到插件的動態鏈接庫中。

GTK+中的GDKPIXBUF庫是專門用來圖像處理部分,它支持多種圖像格式和動畫,如常見的TIFF,JPEG,PNG,GIF等圖像格式。

在Linux中要把圖像保存到插件中去,首先要將圖像轉為C語言代碼格式,即生成about.h文件,它是由下面命令生成的:

gdk-pixbuf-csource --raw gnome-gmush.png > about.h


gdk-pixbuf-csource是GTK+ 2.0中提供的一個將圖像文件轉換成GdkPixbufs結構格式的C源代碼工具,參數--raw分別表示禁止生成的圖像數據保持運行時的長度,通過輸出 重定向,將生成的源代碼保存到about.h文件中去,再加入包含語句#include "about.h",如此就可以將圖像資源保存到庫文件中來了。

下面是生成的about.h的開始部分:

/* GdkPixbuf RGBA C-Source image dump */
static const guint8 my_pixbuf[] = 
{ ""
  /* Pixbuf magic (0x47646b50) */
  "GdkP"
  /* length: header (24) + pixel_data (9216) */
  "//0//0$//30"
  /* pixdata_type (0x1010002) */
  "//1//1//0//2"
  /* rowstride (192) */
  "//0//0//0//300"
  /* width (48) */
  "//0//0//0""0"
  /* height (48) */
  "//0//0//0""0"
  /* pixel_data: */


在這裏我們可以找到文件中圖像的數據長度--9216和文件頭的長度--24,還有文件中圖像數據結構的名稱my_pixbuf,這是創建時默認的名稱,如果有多個圖像,可以指定不同的名稱。如此,我們下面的語句就可以創建圖像對象了。

pix = gdk_pixbuf_new_from_inline(9216+24, my_pixbuf, TRUE, NULL);


同樣可以用下面的語句來創建圖像控件:

image = gtk_image_new_from_pixbuf(pix);



測試插件

分析完以上插件代碼,我們可以寫一個測試程序,來測試插件的執行情況。

//module.c
#include <gtk/gtk.h>
#include <gmodule.h>
typedef void (*About) (void);
typedef void (*Dialog) (gchar* message);
GModule *module;
void	on_open(GtkWidget *button, gpointer data)
{
	module = g_module_open("./libhello.so", 0);
	if(module == NULL)
		g_error("ERROR:%s", g_module_error());
	gtk_widget_set_sensitive(button, FALSE);
}
void	on_dialog(GtkWidget *button, gpointer data)
{
	Dialog dialog;
	if(g_module_symbol(module,"create_message_dialog",(gpointer*)&dialog))
	{
		dialog("這是一個測試用的消息對話框。");
	}
	else
	{
		g_error("ERROR:%s", g_module_error());
	}	
}
void	on_about(GtkWidget *button, gpointer data)
{
	About about;
	if(g_module_symbol(module,"about",(gpointer*)&about))
	{
		about();
	}
	else
	{
		g_error("ERROR:%s", g_module_error());
	}
}
void	on_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
	g_module_close(module);
	gtk_main_quit();
}
int	main(int argc, char* argv[])
{
	GtkWidget *window, *vbox, *button;
	gtk_init(&argc, &argv);
	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title(GTK_WINDOW(window),"插件功能 -- 動態鏈接庫調用");
	g_signal_connect(G_OBJECT(window),"delete_event",
			G_CALLBACK(on_delete_event),NULL);
	gtk_container_set_border_width(GTK_CONTAINER(window),10);
	vbox = gtk_vbox_new(FALSE,0);
	gtk_container_add(GTK_CONTAINER(window),vbox);
	button = gtk_button_new_with_label("調用插件");
	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
	g_signal_connect(G_OBJECT(button), "clicked",
			G_CALLBACK(on_open), NULL);
	button = gtk_button_new_with_label("顯示信息對話框");
	gtk_box_pack_start(GTK_BOX(vbox),button,FALSE,FALSE,0);
	g_signal_connect(G_OBJECT(button),"clicked",
			G_CALLBACK(on_dialog),NULL);
	button = gtk_button_new_with_label("顯示關於對話框");
	gtk_box_pack_start(GTK_BOX(vbox),button,FALSE,FALSE,0);
	g_signal_connect(G_OBJECT(button),"clicked",
			G_CALLBACK(on_about),NULL);
	gtk_widget_show_all(window);
	gtk_main();
	return FALSE;
}


上面的代碼中我們創建了三個按鈕,第一個按鈕上顯示" 調用插件",當按下此按鈕後,程序調用g_module_open函數來打開插件,如果指定的插件對象文件libhello.so存在則設置此按鈕使之功 能失效。不存在或打開失敗則調用g_module_error函數,顯示出錯信息。

另兩個按鈕分別是"顯示關於對話框"和"顯示信息對話框",當點擊它們時分別調用插件中的about函數和create_message_dialog函數。

在程序退出的時候(on_delete_event),我們調用g_module_close函數來關閉打開的插件libhello.so。





調用插件中的函數

上面代碼中最關鍵的部分就是調用插件中的函數,也是我們應用插件的目標,所以在測試代碼中我們自定義了兩個函數指針類型,Dialog和About,如下所示:

typedef void (*About) (void);
typedef void (*Dialog) (gchar* message);


它的格式和我們在插件頭文件hello.h中聲明的函數是一樣的:

void	create_message_dialog(gchar* message);
void	about(void);


函數g_module_symble來取得插件中的函數指針,調用這一函數的前題是我們聲明的函數指針的格式和插件中的定義的函數的格式要相同,如上面的定義。

這樣,當我們成功打開插件後,就可以用g_module_symble函數來取得插件中的函數指針了,如在on_about函數中,首先聲明一個函數指針型自定義類型變量:

About about; //此時about 即為一個函數指針


然後用函數g_module_symbol來取得插件中的about函數的指針,並賦給about:

g_module_symbol(module,"about",(gpointer*)&about)


這樣的話,再執行about()函數,就會執行插件中的about函數,顯示出關於對話框,當然我們完全可以用不同的函數名,如用dialog來取得create_message_dialog函數。



編譯

由於在編譯測試代碼的同時還要編譯插件,我們編寫了一個簡單的Makefile文件,它先編譯輸出libhello.so這個插件文件,然後輸出測試文件module,這樣的話在執行測試時就可以找到插件文件libhello.so了。

Makefile文件內容如下:

CC = gcc
all: module.c libhello.so
	$(CC) `pkg-config --cflags --libs gtk+-2.0` module.c -o module
libhello.so: hello.c hello.h about.h
	$(CC) `pkg-config --cflags gtk+-2.0` -c -fPIC -DPIC hello.c  -o hello.lo
	$(CC) `pkg-config --libs gtk+-2.0` hello.lo -shared -o libhello.so




測試結果

在終端中輸入命令./module,就會顯示下面的主窗口:




點擊主窗口中的調用插件按鈕,就會在終端中輸出以下內容:

插件./libhello.so初始化成功,已經調入!


這説明成功的執行了插件中的g_module_check_init函數。再點擊"顯示信息對話框",就會出現下面的窗口:




點擊"顯示關於對話框"就會顯示下面的窗口:




在程序調用插件中的函數的時候,函數會自動調用插件中的資源,這也是為什麼在關於對話框中會顯示圖像樣原因了。

當點擊關閉按鈕退出測試程序時,就會在終端上顯示如下內容:

插件./libhello.so已成功缷載!


這説明成功執行了插件代碼hello.c中的g_module_unload函數,插件卸載完畢。

(本文的源代碼在RedHat Linux 9.0的GNOME桌面環境下編譯通過)