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