GObject 学习笔记汇总

GObject Introspection 的作用与意义

Garfileo posted @ 2012年2月20日 11:22 in GObject 笔记 with tags GObject-introspection Gjs , 11301 阅读

GObject Introspection(简称 GI)用于产生与解析 C 程序库 API 元信息,以便于动态语言(或托管语言)绑定基于 C + GObject 的程序库。

GI:约定 + 机制

为了正确的生成 C 库的 API 元信息,GI 要求 C 库的实现者要么使用一种特定的代码注释规范,即 Gtk-Doc [1],要么使用带有类型自省功能的 GObject 库。

首先来看如何基于 Gtk-Doc 注释产生 API 元信息,见下面的示例:

#ifndef FOO_H
#define FOO_H

void foo_hello (void);

#endif
#include <stdio.h>
#include "foo.h"

/**
 * foo_hello:
 *
 * This function just for test.
 */
void
foo_hello (void)
{
        printf ("hello foo!\n");
}

使用 gcc 编译上述 C 代码,生成一个共享库:

$ gcc -fPIC -shared foo.c -o libfoo.so

然后使用 g-ir-scanner 产生 libfoo.so 库的 API 元信息:

$ 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 元信息,如下:

<?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 文件转化为二进制格式,例如:

$ g-ir-compiler Foo-1.0.gir -o Foo-1.0.typelib

一旦有了 Typelib 格式的文件,我们就可以通过它来调用它所关联的程序库的 API,例如:

#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 文件,可使用以下命令:

$ 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):

#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
#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);
}

使用下面命令生成共享库 libKbBibtex.so:

$ gcc -fPIC -shared `pkg-config --cflags --libs gobject-2.0` \
    KbBibtex.c -o libKb.so

所生成的 GIR 文件内容如下:

<?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/ 目录:

$ 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 所在目录。

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 方法。

$ 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

  • 无匹配
tolbkni 说:
2012年2月20日 21:33

解释的很清楚很仔细,很好的了解 GI 的入门知识。

Mike 说:
2012年2月22日 18:18

感觉这个文是原来的GObject系列最后一篇的翻版?。。。唯一多的似乎就是有C的内容了。。。

Avatar_small
Garfileo 说:
2012年2月26日 10:32

@Mike: 这篇比上一篇讲的要全面一些。

smon 说:
2012年4月04日 21:17

楼主的vi配置的很舒服,如果方便能发一份配置文件给我吗,多谢啦~~


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter