GObject 學習筆記彙總 GObject Introspection

http://garfileo.is-programmer.com/2012/2/20/gobject-introspection-introduction.32200.html
http://garfileo.is-programmer.com/2011/3/28/a-simple-example-for-gobject-introspection.25662.html

GObject Introspection 的作用與意義

Garfileo posted @ 2012年2月20日 11:22 in GObject 筆記 with tags GObject-introspection Gjs , 2748 閱讀

GObject Introspection(簡稱 GI)用於產生與解析 C 程序庫 API 元信息,以便於動態語言(或託管語言)綁定基於 C + GObject 的程序庫。

GI:約定 + 機制

爲了正確的生成 C 庫的 API 元信息,GI 要求 C 庫的實現者要麼使用一種特定的代碼註釋規範,即 Gtk-Doc[1],要麼使用帶有類型自省功能的 GObject 庫。

首先來看如何基於 Gtk-Doc 註釋產生 API 元信息,見下面的示例:

?
foo.h
1
2
3
4
5
6
#ifndef FOO_H
#define FOO_H
 
void foo_hello (void);
 
#endif
?
foo.c
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 元信息,如下:

?
Foo-1.0.gir
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"
            xmlns="http://www.gtk.org/introspection/core/1.0"
            xmlns:c="http://www.gtk.org/introspection/c/1.0"
            xmlns:glib="http://www.gtk.org/introspection/glib/1.0">
  <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,例如:

?
main.c
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 文件內容如下:

?
KbBibtex-1.0.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"
            xmlns="http://www.gtk.org/introspection/core/1.0"
            xmlns:c="http://www.gtk.org/introspection/c/1.0"
            xmlns:glib="http://www.gtk.org/introspection/glib/1.0">
  <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 所在目錄。

?
test.js
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 方法。

?
運行 test.js
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


發佈了20 篇原創文章 · 獲贊 5 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章