2003 年 3 月 01 日
很多朋友都知道,在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桌面环境下编译通过)