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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值