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 脚本写的。
转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com
2011年3月28日 17:08
很好,我要的就是这篇
2011年3月31日 10:42
因为学习latex看过你写的文稿。今天整理时发现了书签里你CU的主页。访问时发现是06年就停止更新了。
后来通过百度google,再次找到is-pragrammer。然后,发现又更新到此处。。
哎。。。。,fans is first faint and then ...
2011年4月02日 09:35
@banxi1988: 居然收藏过我的 CU 主页
2011年6月12日 14:49
如何与Autotools集成呢?就是自动产生gir文件?
2011年6月12日 14:56
@Mike Ma: 直接在 Makefile.am 文件里按照 makefile 的规则来调用它就可以。
2011年6月12日 15:25
@Mike Ma: 自己折腾出来了,唉,又留下了一些垃圾
2011年10月13日 16:20
早就听说了这么强大的东西了,一直没有机会尝试一下,看了博客的文章后更加激发了我去尝试的热情。