最近更新: 2011-05-05

Vala 與 GObject Introspection 的不搭配狀況

Vala 與 GObject Introspection 都是 GNOME 3 平台中的重要角色。 Vala 為 C 語言程序員提供了易用的 GObject 設計工具。GObject Introspection 則是銜結不同程式語言所實作的 GObject 項目的橋樑。我在《ICOS 2010 記事》中,便提到像 gjs/seed 這些新興的 JavaScript 解譯器,可以透過 GObject Introspection 調用既有的 GLib 項目。 理論上這兩套工具要能搭配互補,但我這陣子在摸索它們的互補途徑時,卻碰了一鼻子灰。

軟體版本
  • Ubuntu Lucid
  • valac 0.12.0
  • gobject-introspection 0.6.8-1

hello.vala 實作了一個非常簡單的類別。我將用它展示我碰到的狀況。

public class Hello {
    public Hello() {
        stdout.printf("new instance\n");
    }
    public void say() {
        stdout.printf("hello\n");
    }
}

下列步驟可以編出 libhello.so 以及一份 GObject Introspection 所需的 gir 文件。


$ valac --library=hello --gir=Hello-0.1.gir \
  -X -shared -X -fPIC -o libhello.so  hello.vala
狀況1: g-ir-compiler 不接受 valac 產生的 gir 文件

我先將上一節由 valac 產出的 Hello-0.1.gir 複製為 Hello-1.0.gir,再交給 g-ir-compiler 編譯為 typelib 中介連結檔。遺憾的是,它開頭就抱怨 gir 文件的版本不對。


$ cp Hello-0.1.gir Hello-1.0.gir
$ g-ir-compiler --output=Hello-1.0.typelib Hello-1.0.gir
Error at line 3, character 1: Unsupported version '1.2'
... 
(其餘省略)

如果只是版本不符的問題那還好辦,然而事情卻沒那麼簡單。仔細修改下來後,我發現 valac 所產出的 gir 文件內容,根本不合 g-ir-compiler 的預期。

在 mailing-list 上,Vala 作者曾抱怨 GObject Introspection 缺乏公開文件,他必須自己解析已有的 gir 文件反推其義。所以才會造成現在這種不相容的狀況。

狀況2: g-ir-scanner 掃描 hello.c 所產生的 gir 文件,沒有類別內容

既然 valac 產出的 gir 文件不合用,那我們再換個方式。我們讓 valac 產出 C 源碼,再將之交由 g-ir-scanner 掃描內容,由 g-ir-scanner 產出 gir 文件。


$ valac -C --library=hello hello.vala

$ LD_LIBRARY_PATH=. g-ir-scanner --namespace=Hello --nsversion=1.0 \
  --include=GObject-2.0 --pkg=gobject-2.0 --library=hello hello.c \
  -o Hello-0.2.gir
$ cat Hello-0.2.gir
... 
(沒有類別內容)

查看了 g-ir-scanner 的輸出結果,我卻發現它沒有類別資訊。

GObject Introspection Annotations 之說明,程式源碼中要有 Gtk-doc 的註記才能被 g-ir-scanner 處理。而 valac 產生的 C 源碼中,並沒有幫我們產生這些註記,是以 g-ir-scanner 也不會產出類別資訊於 gir 文件。

附帶一提, g-ir-scanner 的 --library-path 參數似乎沒有作用。就算我指定了這個參數,它仍然找不到 libhello.so。 於是我直接以環境變數 LD_LIBRARY_PATH 指定 libhello.so 所在路徑。

手動編輯 Hello-1.0.gir

由於以上兩種工具都不能幫我產生可用的 gir 文件,所以我目前只能自己動手編輯了。 這個工作缺乏有效的參考文件,只能邊改邊嘗試。

以下為我編輯出來符合 Hello 類別的 gir 文件。

<?xml version="1.0"?>
<repository version="1.0" 
    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">
<package name="hello"/>
<namespace name="Hello" shared-library="hello" version="1.0">
<class name="Hello" c:type="Hello" 
    glib:type-name="Hello" glib:get-type="hello_get_type">
    <constructor name="new" c:identifier="hello_new">
	    <return-value transfer-ownership="full">
		    <type name="Hello" c:type="Hello*"/>
	    </return-value>
    </constructor>
    <method name="say" c:identifier="hello_say">
	    <return-value transfer-ownership="none">
		    <type name="none"/>
	    </return-value>
    </method>
</class>
</namespace>
</repository>

$ g-ir-compiler --output=Hello-1.0.typelib Hello-1.0.gir
狀況3: Vala 定義的類別內容,無法透過 GObject Introspection 機制配置實體

雖然我千辛萬苦地弄出了 gir 文件,並產出了 typelib 的中介連結檔。但是實際使用時,卻不能配置出實體。 以下以 gjs 為示範:

var gi = imports.gi.GIRepository; // import namespace
gi.IRepository.prepend_search_path(".");

const h = imports.gi.Hello;
print("GJS: namespace of Hello: " + h);

var o = new h.Hello();
print("GJS: type of o is " + o);

o.say();

執行結果:


$ gjs test-hello.js
(gjs-console.real:12062): GLib-GObject-CRITICAL **: g_object_newv: assertion `G_TYPE_IS_OBJECT (object_type)' failed

(gjs-console.real:12062): GLib-GObject-CRITICAL **: g_object_is_floating: assertion `G_IS_OBJECT (object)' failed

(其餘省略)

但是直接用 C 語言,按照 GObject 設計慣例 所實作的類別內容,使用相同的 Hello-1.0.gir,卻可正常配置實體。

#include <stdio.h>
#include <glib-object.h>
 
#define TYPE_HELLO (hello_get_type ())
#define HELLO(object) G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_HELLO, Hello)
#define IS_HELLO(object) G_TYPE_CHECK_INSTANCE_TYPE ((object), TYPE_HELLO))
#define HELLO_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), TYPE_HELLO, HelloPrivate))
#define HELLO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_HELLO, HelloClass))
#define IS_HELLO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_HELLO))
#define HELLO_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), TYPE_HELLO, HelloClass))

typedef struct _Hello Hello;
struct _Hello {
    GObject parent;
};

typedef struct _HelloClass HelloClass;
struct _HelloClass {
    GObjectClass parent_class;
};

GType hello_get_type (void);
G_DEFINE_TYPE (Hello, hello, G_TYPE_OBJECT);

typedef struct _HelloPrivate HelloPrivate;
struct _HelloPrivate {
};
 
static void
hello_init (Hello *self)
{
    printf("obj_init\n");
}

static void
hello_class_init (HelloClass *klass)
{
    printf("class_init\n");
//    g_type_class_add_private (klass, sizeof (HelloPrivate));
    GObjectClass *base_class = G_OBJECT_CLASS (klass);
}
 
void
hello_say (Hello *self)
{
    printf("hello world\n");
}

編譯與執行結果如下列所示:


$ gcc `pkg-config --cflags --libs gobject-introspection-1.0` \
  hello_pure.c -shared -fPIC -o libhello.so 
$ g-ir-compiler --output=Hello-1.0.typelib Hello-1.0.gir
$ gjs test-hello.js
GJS: namespace of Hello: [object GIRepositoryNamespace]
class_init
obj_init
GJS: type of o is [object instance proxy GIName:Hello.Hello jsobj]
hello world

我雖然嘗試比較過 Vala 產出的 hello.c 與我直接用 C 語言撰寫的 hello_pure.c,但目前還看不出是在哪個環節有差異,才導致 Vala 產生的 GObject 類別不能透過 GObject Introspection 實體化。

但是 Vala 設計的一般函數內容(不歸屬於類別的純函數),其他語言仍可透過 GObject Introspection 調用。

Vala 與 GObject Introspection 這兩套工具,目前還有許多地方未能互相搭配。要讓它們在 GNOME 3 平台上共舞,現階段似乎還不是時候。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/15619165.html