轉自: http://garfileo.is-programmer.com/2011/3/28/a-simple-example-for-gobject-introspection.25662.html
GObject Introspection 的簡單示例
這段時間以來,一直在跟 GObject 死磕。除了有點枯燥與乏味之外,也沒什麼不適,就是一堆繁瑣但還算是直觀的 C 代碼罷了。現在,我想讓 GObject 單調的學習過程略微輕鬆一下。畢竟春天正在到來,窗外的迎春花已經怒放了。記得前段時間爲自己開始學習 GObject 寫了一篇序言“要相信 GObject 是有用並且簡單的”,其中引用了 Thinker 的一篇文章“GObject Introspection 帶來一些希望”。本文通過一個很小的實例,演示一下 GObject 程序如何通過 GObject Introspection 與 JavaScript 腳本進行結合。
KbBibtex 類的回放
我們曾經在“溫故而知新”這篇文檔中構造了一個 KbBibtex 類,其聲明文件 kb-bibtex.h 如下:
#ifndef KB_BIBTEX_H
#define KB_BIBTEX_H
#include <glib-object.h>
#define KB_TYPE_BIBTEX (kb_bibtex_get_type ())
#define KB_BIBTEX(object) \
G_TYPE_CHECK_INSTANCE_CAST ((object), KB_TYPE_BIBTEX, KbBibtex)
#define KB_IS_BIBTEX(object) \
G_TYPE_CHECK_INSTANCE_TYPE ((object), KB_TYPE_BIBTEX))
#define KB_BIBTEX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST ((klass), KB_TYPE_BIBTEX, KbBibtexClass))
#define KB_IS_BIBTEX_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE ((klass), KB_TYPE_BIBTEX))
#define KB_BIBTEX_GET_CLASS(object) \
(G_TYPE_INSTANCE_GET_CLASS ((object), KB_TYPE_BIBTEX, KbBibtexClass))
typedef struct _KbBibtex KbBibtex;
struct _KbBibtex {
GObject parent;
};
typedef struct _KbBibtexClass KbBibtexClass;
struct _KbBibtexClass {
GObjectClass parent_class;
};
GType kb_bibtex_get_type (void);
void kb_bibtex_printf (KbBibtex *self);
#endif
其定義文件 kb-bibtex.c 如下:
#include "kb-bibtex.h"
G_DEFINE_TYPE (KbBibtex, kb_bibtex, G_TYPE_OBJECT);
#define KB_BIBTEX_GET_PRIVATE(object) (\
G_TYPE_INSTANCE_GET_PRIVATE ((object), KB_TYPE_BIBTEX, KbBibtexPrivate))
typedef struct _KbBibtexPrivate KbBibtexPrivate;
struct _KbBibtexPrivate {
GString *title;
GString *author;
GString *publisher;
guint year;
};
enum PROPERTY_BIBTEX {
PROPERTY_0,
PROPERTY_TITLE,
PROPERTY_AUTHOR,
PROPERTY_PUBLISHER,
PROPERTY_YEAR,
N_PROPERTIES
};
static void
kb_bibtex_set_property (GObject *object, guint property_id,
const GValue *value, GParamSpec *pspec)
{
KbBibtex *self = KB_BIBTEX (object);
KbBibtexPrivate *priv = KB_BIBTEX_GET_PRIVATE (self);
switch (property_id) {
case PROPERTY_TITLE:
if (priv->title)
g_string_free (priv->title, TRUE);
priv->title = g_string_new (g_value_get_string (value));
break;
case PROPERTY_AUTHOR:
if (priv->author)
g_string_free (priv->author, TRUE);
priv->author = g_string_new (g_value_get_string (value));
break;
case PROPERTY_PUBLISHER:
if (priv->publisher)
g_string_free (priv->publisher, TRUE);
priv->publisher = g_string_new (g_value_get_string (value));
break;
case PROPERTY_YEAR:
priv->year = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
kb_bibtex_get_property (GObject *object, guint property_id,
GValue *value, GParamSpec *pspec)
{
KbBibtex *self = KB_BIBTEX (object);
KbBibtexPrivate *priv = KB_BIBTEX_GET_PRIVATE (self);
GString *similar = NULL;
switch (property_id) {
case PROPERTY_TITLE:
g_value_set_string (value, priv->title->str);
break;
case PROPERTY_AUTHOR:
g_value_set_string (value, priv->author->str);
break;
case PROPERTY_PUBLISHER:
g_value_set_string (value, priv->publisher->str);
break;
case PROPERTY_YEAR:
g_value_set_uint (value, priv->year);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
kb_bibtex_init (KbBibtex *self)
{
}
static void
kb_bibtex_class_init (KbBibtexClass *klass)
{
g_type_class_add_private (klass, sizeof (KbBibtexPrivate));
GObjectClass *base_class = G_OBJECT_CLASS (klass);
base_class->set_property = kb_bibtex_set_property;
base_class->get_property = kb_bibtex_get_property;
GParamSpec *properties[N_PROPERTIES] = {NULL,};
properties[PROPERTY_TITLE] =
g_param_spec_string ("title",
"Title",
"Bibliography title",
NULL,
G_PARAM_READWRITE);
properties[PROPERTY_AUTHOR] =
g_param_spec_string ("author",
"Author",
"Bibliography author",
NULL,
G_PARAM_READWRITE);
properties[PROPERTY_PUBLISHER] =
g_param_spec_string ("publisher",
"Publisher",
"Bibliography Publisher",
NULL,
G_PARAM_READWRITE);
properties[PROPERTY_YEAR] =
g_param_spec_uint ("year",
"Year",
"Bibliography year",
0,
G_MAXUINT,
0,
G_PARAM_READWRITE);
g_object_class_install_properties (base_class, N_PROPERTIES, properties);
}
void
kb_bibtex_printf (KbBibtex *self)
{
gchar *title, *author, *publisher;
guint year;
g_object_get (G_OBJECT (self),
"title", &title,
"author", &author,
"publisher", &publisher,
"year", &year,
NULL);
g_printf (" Title: %s\n"
" Author: %s\n"
"Publisher: %s\n"
" Year: %d\n", title, author, publisher, year);
g_free (title);
g_free (author);
g_free (publisher);
}
通過這次的回放,我不禁自得於我還不錯的體力,可以寫這麼多的 C 代碼 :(
KbBibtex 類的調用者
在“溫故而知新”文檔中,KbBibtex 類的調用者是 main.c 源文件中的 main 函數。但是,在本文中,KbBibtex 類的調用者則另有其人,見下面的代碼:
const Kb = imports.gi.Kb;
function start()
{
let bibtex = new Kb.Bibtex ({title:"The {\\TeX}Book",
author:"Knuth, D. E.",
publisher:"Addison-Wesley Professional",
year:1984});
bibtex.printf ();
}
@#¥%……這就是傳說中的 JavaScript 代碼,它調用了 KbBibtex 類。這些代碼等價於下面的 C 代碼:
#include "kb-bibtex.h"
int
main (void)
{
g_type_init ();
KbBibtex *entry = g_object_new (KB_TYPE_BIBTEX,
"title", "The {\\TeX}Book",
"author", "Knuth, D. E.",
"publisher", "Addison-Wesley Professional",
"year", 1984,
NULL);
kb_bibtex_printf (entry);
g_object_unref (entry);
return 0;
}
現在,我們將上述的 JavaScript 腳本命名爲 main.js。
JavaScript 的解析程序
我們常見的 JavaScript 代碼是嵌在網頁裏被瀏覽器解析執行的,雖然 main.js 中的 JavaScript 代碼和網頁中的 JavaScript 代碼是兩碼事,但是它也需要解析器,這個解析器的名字叫做 Gjs。
Gjs 主要基於 Firefox 的 JavaScript 引擎 SpiderMonkey 和 GObject Introspection 實現,前者可以解析執行 JavaScript 腳本,後者可以幫助 JavaScript 腳本調用 GObject 子類的方法。
下面,便基於 Gjs 構建一個可執行 main.js 中的 start 函數的 C 程序:
#include <gjs/gjs.h>
#include <girepository.h>
int
main(int argc, char *argv[])
{
g_type_init ();
gchar *js_path[] = {"./"};
gchar *gir_path = "./";
GOptionContext *ctx = g_option_context_new (NULL);
g_option_context_add_group (ctx, g_irepository_get_option_group ());
g_option_context_parse (ctx, &argc, &argv, NULL);
/* 設置 typelib 文件查詢路徑 */
g_irepository_prepend_search_path (gir_path);
/* 設置 JavaScript 文件路徑並解析執行指定的 JavaScript 腳本 */
GjsContext *gjs_ctx = gjs_context_new_with_search_path (js_path);
gjs_context_eval (gjs_ctx,
"const Main = imports.main; Main.start();", /* 要執行的代碼 */
-1,
"<main>", /* main.js 文件名 */
NULL,
NULL);
return 0;
}
上述代碼中,我添加了一些註釋。對於 Gjs 和 GObject Introspection 的用法,目前我也僅知道這些。現在,我們只需要關注一點,那就是在 main.c 文件中,我們沒有一行代碼是與前面的 KbBibtex 類顯式相關。這個 main.c 完全是一個獨立的程序,它的主要職責就是解析執行一個 JavaScript 腳本。
編譯與運行
下面,開始編譯這個混雜了 JavaScript 腳本的程序。
首先,將上述所有代碼的相關文件放在同一個目錄下(主要是方便測試,實際工程中則不然),文件清單如下:
$ cd test && ls
kb-bibtex.c kb-bibtex.h main.c main.js
編譯 kb-bibtex.c 與 main.c,並將它們的中間文件連到一起:
# 編譯 kb-bibtex.o
$ gcc -c kb-bibtex.c $(pkg-config --cflags gobject-2.0)
# 編譯 main.o
$ gcc -c main.c $(pkg-config --cflags gjs-1.0)
# 連接 main.o 與 kb-bibtex.o,輸出 test 程序
$ gcc main.o kb-bibtex.o -o test \
$(pkg-config --libs gjs-1.0 gobject-introspection-1.0)
當然,我們可以不必像上面那麼麻煩,直接執行下面的命令即可:
$ gcc kb-bibtex.c main.c -o test $(pkg-config --cflags --libs \
gjs-1.0 gobject-introspection-1.0)
然後使用 g-ir-scanner 工具產生 Kb-1.0.gir 文件:
$ g-ir-scanner --namespace=Kb --nsversion=1.0 \
--include=GObject-2.0 --pkg=gobject-2.0 \
--program=test kb-bibtex.h kb-bibtex.c \
-o Kb-1.0.gir
生成的 Kb-1.0.gir 主要是被 g-ir-compiler 工具使用,產生二進制文件 Kb-1.0.typelib,如下:
$ g-ir-compiler Kb-1.0.gir -o Kb-1.0.typelib
Kb-1.0.gir 文件是 XML 格式,主要用於記錄 KbBibtex 類的詳細信息,g-ir-compiler 所做的工作是將其處理爲二進制格式的 Kb-1.0.typelib 文件,目的在於後者體積更小,解析更快!
上述的編譯過程,主要是獲得了一個 test 程序和一個 Kb-1.0.typelib 二進制文件。現在,執行 test 程序可得到以下輸出:
$ ./test
Title: The {\TeX}Book
Author: Knuth, D. E.
Publisher: Addison-Wesley Professional
Year: 1984
這與“溫故而知新”文檔中測試程序的輸出結果是相同的。
現在,我們一定要回想一下,test 程序是對 kb-bibtex.c 與 main.c 的編譯和連接而成的,其中 kb-bibtex.c 中沒有調用 KbBibtex 類的的任何方法,main.c 也同樣,並且 main.c 中只是解析執行了 main.js 腳本。所以,可以肯定 main.js 不僅完成了一個 KbBibtex 對象的構造還調用了它的 printf 方法。
那麼,這一切意味着什麼?
這當然是意味着所有基於 GObject 庫的 C 程序 {@#¥%} 都可交由 g-ir-scanner 與 g-ir-compiler 生成 *.gir 與 *.typelib 文件,然後所有的編程語言(Python、Lua、Ruby、Haskell、JavaScript……),只要它們可以與 C 語言混合編程,那麼都可以基於 GObject Introspection 與一些 *.typelib 文件調用 {@#¥%} 程序中的函數。
嗯,這樣究竟有什麼好處?
自然是 C 程序員任勞任怨的去做最底層的工作,動態語言和函數語言的愛好者有機會使用他們最衷愛的語言去寫上層模塊,比如大量的擴展/插件。例如 GNOME 3 桌面的核心組件 gnome shell 的外圍部分(例如 Overview 視圖、工作區、面板等),都是使用 JavaScript 腳本寫的。