http://garfileo.is-programmer.com/2011/3/28/a-simple-example-for-gobject-introspection.25662.html
GObject Introspection 的作用与意义
GObject Introspection(简称 GI)用于产生与解析 C 程序库 API 元信息,以便于动态语言(或托管语言)绑定基于 C + GObject 的程序库。
GI:约定 + 机制
为了正确的生成 C 库的 API 元信息,GI 要求 C 库的实现者要么使用一种特定的代码注释规范,即 Gtk-Doc[1],要么使用带有类型自省功能的 GObject 库。
首先来看如何基于 Gtk-Doc 注释产生 API 元信息,见下面的示例:
1
2
3
4
5
6
|
#ifndef FOO_H
#define FOO_H
void
foo_hello (
void
);
#endif
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <stdio.h>
#include "foo.h"
/**
* foo_hello:
*
* This function just for test.
*/
void
foo_hello (
void
)
{
printf
(
"hello foo!\n"
);
}
|
使用 gcc 编译上述 C 代码,生成一个共享库:
1
|
$ gcc -fPIC -shared foo.c -o libfoo.so
|
然后使用 g-ir-scanner 产生 libfoo.so 库的 API 元信息:
1
|
$ g-ir-scanner --namespace=Foo --nsversion=1.0 --library=foo foo.h foo.c -o Foo-1.0.gir
|
所生成的 Foo-1.0.gir 文件是 XML 格式,其中记录了 libfoo.so 库的 API 元信息,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<?
xml
version
=
"1.0"
?>
<!-- This file was automatically generated from C sources - DO NOT EDIT!
To affect the contents of this file, edit the original C definitions,
and/or use gtk-doc annotations. -->
<
repository
version
=
"1.2"
<
namespace
name
=
"Foo"
version
=
"1.0"
shared-library
=
"libfoo.so"
c:identifier-prefixes
=
"Foo"
c:symbol-prefixes
=
"foo"
>
<
function
name
=
"hello"
c:identifier
=
"foo_hello"
>
<
doc
xml:whitespace
=
"preserve"
>This function just for test.</
doc
>
<
return-value
transfer-ownership
=
"none"
>
<
type
name
=
"none"
c:type
=
"void"
/>
</
return-value
>
</
function
>
</
namespace
>
</
repository
>
|
认真观察 Foo-1.0.gir 文件内容,可以发现其中有一部分信息是我们在运行 g-ir-scanner 命令时提供的,还有一部分信息来源于 foo.c 源文件中的代码注释。
Foo-1.0.gir 文件的作用就是以一种固定的格式描述 libfoo.so 库的 API 元信息,我们可以将这种文件称为 GIR 文件。有了 GIR 文件,就可以不用再通过库的头文件来获取所需的函数符号了。
在实际使用中,加载并解析 Foo-1.0.gir 这样的 XML 文件,效率较低,因此 GObject Introspection 定义了一种二进制格式,即 Typelib 格式,并提供 g-ir-compiler 工具将 GIR 文件转化为二进制格式,例如:
1
|
$ g-ir-compiler Foo-1.0.gir -o Foo-1.0.typelib
|
一旦有了 Typelib 格式的文件,我们就可以通过它来调用它所关联的程序库的 API,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#include <girepository.h>
int
main (
void
)
{
GIRepository *repository;
GError *error = NULL;
GIBaseInfo *base_info;
GIArgument retval;
g_type_init();
g_irepository_prepend_search_path (
"./"
);
repository = g_irepository_get_default ();
g_irepository_require (repository,
"Foo"
,
"1.0"
, 0, &error);
if
(error) {
g_error (
"ERROR: %s\n"
, error->message);
return
1;
}
base_info = g_irepository_find_by_name (repository,
"Foo"
,
"hello"
);
if
(!base_info) {
g_error (
"ERROR: %s\n"
,
"Could not find Foo.hello"
);
return
1;
}
if
(!g_function_info_invoke ((GIFunctionInfo *)base_info,
NULL,
0,
NULL,
0,
&retval,
&error)) {
g_error (
"ERROR: %s\n"
, error->message);
return
1;
}
g_base_info_unref (base_info);
return
0;
}
|
上述代码所完成的主要工作如下:
g_irepository_prepend_search_path
函数将当前目录添加到 Typelib 文件搜索目录列表;g_irepository_get_default
函数获取默认的 GIRepository 实例;g_irepository_require
函数可载入指定的 Typelib 文件;g_irepository_find_by_name
函数可从给定的 GIRepository 实例中根据指定的命名空间与 API 名称获取 API 的元信息,将其存入一个GIBaseInfo
实例并返回;g_function_info_invoke
函数可以执行GIBaseInfo
实例中所记载的 API 元信息对应的 API,本例即foo_hello
函数;g_base_info_unref
函数用于释放一个GIBaseInfo
实例的引用。
编译上述的 test.c 文件,可使用以下命令:
1
|
$ gcc `pkg-config --cflags --libs gobject-introspection-1.0`
test
.c -o
test
|
运行所生成的程序,它便会正确的调用 libfoo.so 库中定义的 foo_hello
函数。
从上面的 libfoo.so 与 Typelib 文件的生成及其应用可以看出,GI 可以根据 C 库的实现者提供的代码注释产生 API 元信息,库的使用者则可以通过 GI 与 API 元信息去调用相应的 API。这样做有什么好处?
试想,假如许多 C 库都采用同一种代码注释规范,那么 g-ir-scanner 就可以生成它们的 API 元信息文件。如果我们使用动态语言(托管语言)对这些 C 库进行绑定时,就不需要对这些 C 库逐一实现绑定,只需对 GI 库进行绑定,通过它来解析 Typelib 文件,从而直接调用这些 C 库 API。也就是说,GI 是让 C 库开发者多做了一点工作,从而显著降低了 C 库绑定的工作量。事实上,GI 并没有让 C 库开发者多做什么工作,因为对 API 进行详细的注释是每个 C 库开发者都要去做的工作,GI 只是要求 C 库开发者使用 Gtk-Doc 格式的注释而已。
使用 Gtk-Doc 格式的注释可以帮助 g-ir-scanner 生成详细且正确的 API 元信息,这是 GI 提供的一种约定。但是如果程序库是基于 GObject 实现的,由于 GObject 类型系统具有自省功能,而 GI 可以利用这一功能在不借助 Gtk-Doc 注释的情况下自动生成『类』(即面向对象编程中的类的概念)的元信息。例如下面的 Bibtex 类[2](命名空间为 Kb
):
使用下面命令生成共享库 libKbBibtex.so:
1
2
|
$ gcc -fPIC -shared `pkg-config --cflags --libs gobject-2.0` \
KbBibtex.c -o libKb.so
|
所生成的 GIR 文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
<?
xml
version
=
"1.0"
?>
<!-- This file was automatically generated from C sources - DO NOT EDIT!
To affect the contents of this file, edit the original C definitions,
and/or use gtk-doc annotations. -->
<
repository
version
=
"1.2"
<
include
name
=
"GLib"
version
=
"2.0"
/>
<
include
name
=
"GObject"
version
=
"2.0"
/>
<
package
name
=
"gobject-2.0"
/>
<
namespace
name
=
"Kb"
version
=
"1.0"
shared-library
=
"libKbBibtex.so"
c:identifier-prefixes
=
"Kb"
c:symbol-prefixes
=
"kb"
>
<
class
name
=
"Bibtex"
c:symbol-prefix
=
"bibtex"
c:type
=
"KbBibtex"
parent
=
"GObject.Object"
glib:type-name
=
"KbBibtex"
glib:get-type
=
"kb_bibtex_get_type"
glib:type-struct
=
"BibtexClass"
>
<
method
name
=
"printf"
c:identifier
=
"kb_bibtex_printf"
>
<
return-value
transfer-ownership
=
"none"
>
<
type
name
=
"none"
c:type
=
"void"
/>
</
return-value
>
</
method
>
<
property
name
=
"author"
writable
=
"1"
transfer-ownership
=
"none"
>
<
type
name
=
"utf8"
/>
</
property
>
<
property
name
=
"publisher"
writable
=
"1"
transfer-ownership
=
"none"
>
<
type
name
=
"utf8"
/>
</
property
>
<
property
name
=
"title"
writable
=
"1"
transfer-ownership
=
"none"
>
<
type
name
=
"utf8"
/>
</
property
>
<
property
name
=
"year"
writable
=
"1"
transfer-ownership
=
"none"
>
<
type
name
=
"guint"
/>
</
property
>
<
field
name
=
"parent"
>
<
type
name
=
"GObject.Object"
c:type
=
"GObject"
/>
</
field
>
</
class
>
<
record
name
=
"BibtexClass"
c:type
=
"KbBibtexClass"
glib:is-gtype-struct-for
=
"Bibtex"
>
<
field
name
=
"parent_class"
>
<
type
name
=
"GObject.ObjectClass"
c:type
=
"GObjectClass"
/>
</
field
>
</
record
>
</
namespace
>
</
repository
>
|
所生成的 GIR 文件中,详细的记录了 Bibtex 类的属性、方法、父类等元信息。对于支持面向对象的动态语言(托管语言)而言,可以根据类的元信息动态生成类,例如 Python 的元类编程 [3] 便支持这种方式。
主流动态语言对 GI 的支持
目前 Python, Ruby, Lua, JavaScript 等动态语言均已实现了 GI 的绑定,可以调用所有支持 GI 的 C 库。
下面来看如何使用 JavaScript 使用上文 libKb.so 库中的类。
首先使用 g-ir-compiler 将 kb-1.0.gir 文件转化为 Typelib 格式,并将其复制到 /usr/lib/girepository-1.0/
目录:
1
2
|
$ g-ir-compiler Kb-1.0.gir -o Kb-1.0.typelib
$
sudo
cp
Kb-1.0.typelib
/usr/lib/girepository-1
.0
|
为了方便测试,也可将 libKb.so 复制到 /usr/lib
目录,或者将下面的 JavaScript 文件置于 libKb.so 所在目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
const Kb = imports.gi.Kb;
function
test ()
{
let bibtex =
new
Kb.Bibtex ({title:
"The {\\TeX}Book"
,
author:
"Knuth, D. E."
,
publisher:
"Addison-Wesley Professional"
,
year:1984});
bibtex.printf ();
}
test ();
|
当我们使用 gjs 运行 test.js 时,便会调用 libKb.so 中定义的 Bibtex
类的 printf
方法。
1
2
3
4
5
|
$ gjs
test
.js
Title: The {\TeX}Book
Author: Knuth, D. E.
Publisher: Addison-Wesley Professional
Year: 1984
|
从上面的示例可以看到,虽然我们没有为 libKb.so 进行 JavaScript 绑定,但是后者的确可以使用前者定义的类。如果你熟悉 Python, Ruby, Lua 等语言的话,也可以像 JavaScript 那样调用 libKb.so 的功能。
GI 就像是一座桥梁,沟通着 C 的世界与动态语言的世界。在 GNOME 项目的推动下,越来越多的 C + GObject 库支持 GI,这意味着动态语言的资源也越来越丰富。在 GI 的技术框架中,用 C + GOBject 实现项目的核心功能,用各种各样的动态语言来构建上层逻辑,是非常容易的事情。
静态语言如何利用 GI?
静态语言没有『元类编程』的功能,所以它是不可能像动态语言那样根据 API 或类的元信息『在线』生成 API 或类的『绑定』。这样看上去,GI 对静态语言似乎意义不大。但是考虑到静态语言可以根据 GI 提供的 C 库的元信息自动生成“离线”的绑定代码,然后将这些代码编译为可用的模块。这样做虽然没有动态语言那般优雅,但是依然显著降低了 C 库绑定的工作量,因为这个过程是完全可以由一个程序自动完成。例如 Haskell 对 GI 的绑定项目——haskell-gi [4],便是利用 GI 产生的元信息自动生成 GLib, Gtk+ 等程序库的绑定代码。
转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com