GLib提供的一个非常有用的特性是动态加载库和使用GModule结构现实地调用这些库中的函数。这个功能在不同平台山表现不同,因此一个跨平台动态加载库的方案是可以让事情简化。这个函数的改进之处,一句话:插件系统的创建。在列表6-10中,一个简单的理论上的插件系统将会被创建。
例子被分成了两个独立的文件:一个是用来做插件,另一个是主应用程序。为了运行这个应用程序,首先您需要作为一个库编译和链接modules-plugin.c。您可以使用如下两个命令来创建库和安装它到标准位置。
gcc –shared modules-plugin.c –o plugin.so `pkg-config --libs glib-2.0` \
`pkg-config --cflags glib-2.0`
sudo mv plugin.so /usr/lib
库的创建通常是由GUN连接器(ld)来完成的,但是使用-shared标志,GCC可以创建共享库。还有,在一些系统中,在您移动插件库之后运行ldconfig是有必要的,因为它将被注册。如果您想使用链接库的目的不只是用GModule加载,您需要这样做。
Listing 6-10. 插件 (模块-plugin.c)
#include <glib.h>
#include <gmodule.h>
G_MODULE_EXPORT gboolean
print_the_message (gpointer data)
{
g_printf ("%s\n", (gchar*) data);
return TRUE;
}
G_MODULE_EXPORT gboolean
print_another_one (gpointer data)
{
g_printf ("%s\n", (gchar*) data);
return TRUE;
}
插件资源文件只包含一个或多个函数,它们由主应用程序加载。因此,在插件源文件中没有必要包含一个main()函数。
插件文件唯一重要的方面是您需要在导出任何您想导出的函数之前包含G_MODULE_EXPORT。如果您不用此宏,GModule将不能从库中加载此函数。
从库中动态加载的函数被称作符号。一个符号仅仅是库中一个函数的指针。您可以像调用其它任何函数一样,以相同的方式调用符号函数。唯一的区别是,当符号函数被调用时,GLib查找到了库中的真正函数并从那个地址开始执行它。
这个方法的好处是,多个应用程序可以同时加载一个库。允许多个应用程序调用的库被称作共享库。在Linux中编译的大部分库都是共享库。
当编译列表6-11中的主文件是,您还需要使用一个可选的链接行,因为您需要链接GModule库。
gcc modules.c –o modules `pkg-config --cflags --libs glib-2.0` \
`pkg-config --cflags --libs gmodule-2.0`
GModule可以通过添加`pkg-config --cflags --libs gmodule-2.0`编译命令很容易地被包含进来。如下例子阐明了如果加载我们刚刚创建并安装的库。列表6-11是一个采用了列表6-10中的动态模块优势的应用程序。
Listing 6-11 加载插件 (modules.c)
#include <gmodule.h>
#include <glib.h>
typedef gboolean (* PrintMessageFunc) (gpointer data);
typedef gboolean (* PrintAnotherFunc) (gpointer data);
int main (int argc,
char *argv[])
{
GModule *module;
PrintMessageFunc print_the_message;
PrintAnotherFunc print_another_one;
gchar *text = "This is some text";
/* Make sure module loading is supported on the user's machine. */
g_assert (g_module_supported ());
/* Open the library and resolve symbols only when necessary. Libraries on
* Windows will have a .dll appendix. */
module = g_module_open ("/usr/lib/plugin.so", G_MODULE_BIND_LAZY);
if (!module)
{
g_error ("Error: %s\n", (gchar*) g_module_error ());
return -1;
}
/* Load the print_the_message() function. */
if (!g_module_symbol (module, "print_the_message",
(gpointer*) &print_the_message))
{
g_error ("Error: %s\n", (gchar*) g_module_error ());
return -1;
}
/* Load the destroy_the_evidence() function. */
if (!g_module_symbol (module, "print_another_one",
(gpointer*) &print_another_one))
{
g_error ("Error: %s\n", (gchar*) g_module_error ());
return -1;
}
/* Run both loaded functions since there were no errors reported loading
* neither the module nor the symbols. */
print_the_message ((gpointer) text);
print_another_one ("Another Message!");
/* Close the module and free allocated resources. */
if (!g_module_close (module))
g_error ("Error: %s\n", (gchar*) g_module_error ());
return 0;
}
不是所有的平台支持GModule结构。因此,如果您创建一个可以在多个平台上编译的应用程序,确认平台是否支持是一个好主意。
是否支持GModule可以调用g_module_supported()进行检查,如果支持返回TRUE。通过使用g_assert(),如果GModule不支持,您可以确认应用程序将要终止。
一旦您确认用户系统中支持GModule,您可以打开调用g_module_open()打开一个库。如果打开模块失败,函数会返回NULL。然而,在失败之前,函数为了找到加载的库会试图多次格式化给定库的名字。为了指定路径,这个包含追加系统默认库前缀G_MODULE_SUFFIX。
GModule* g_module_open (const gchar *library,
GModuleFlags flags);
g_module_open()中的第二个参数指定了一个或多个模块的标志,它通知GModule如果处理标志。一般有3个可选的GModuleFlags枚举值:
. G_MODULE_BIND_LAZY:当模块被默认加载时,标志应高被捆绑。然而,这通知Glib,当需要的时候找到对应的符号。
. G_MODULE_BIND_LOCAL:不要将标志放在全局空间中,在大部分系统中这是默认的。
. G_MODULE_BIND_MASK:GModule标志的掩码。
在您的应用程序的任何地方,您可以调用g_module_error(),它会返回一个用来描述发生的最后错误的可读符串。如果函数返回了一个异常数值,输出这个消息到屏幕是个好主意。
如果模块被成功加载,g_module_symbol()可以被用来加载库中的任何函数,这是通过使用G_MODULE_EXPORT才可以的。如果标志被成功加载,函数会返回TRUE.
gboolean g_module_symbol (GModule *module,
const gchar *symbol_name,
gpointer *symbol);
g_module_symbol()的第二个参数是您想从库中加载的函数的全名。最后一个参数是内存中用来存储函数的指针。您为载入的函数和指针,指定同样的参数和返回值,可能会带来问题,这是个基本问题。
当您使用完GModule对象之后,通常是当应用程序将要关闭或插件正在被卸载时,调用g_module_close()。如果析构对象成功会返回TRUE。
如果您确认模块从来不被加载,您可以忽略调用g_module_close(),而调用g_module_make_resident()。使用此函数请小心,因为在调用这个之后,卸载模块是不可能的。