GObject Introspection対応ライブラリーの作り方 - 2013-12-16 - ククログ

ククログ

株式会社クリアコード > ククログ > GObject Introspection対応ライブラリーの作り方

GObject Introspection対応ライブラリーの作り方

GNOME Advent Calendar 2013 16日目の記事です。

スクリプト言語の拡張機能をほぼ全自動で作ることができるGObject Introspectionに対応したライブラリーの作り方を紹介します。ライブラリーはCで実装します。

GObject Introspectionに対応するとうれしいこと

最初に、GObject Introspectionに対応するとうれしいことを説明します。

Cで書かれたライブラリーがGObject Introspectionに対応していると、各種スクリプト言語の拡張機能を1つずつ作る必要がありません。実際にどのようになるかみてみましょう。

この記事では次のAPIを提供するCのライブラリー「Sample」を作ります。このライブラリーはGTK-Docの使い方を説明したときに作ったものです。詳細はリンク先を参照してください。

struct _SampleGreeter
{
    GObject parent_instance;
};

SampleGreeter *sample_greeter_new      (void);
const gchar   *sample_greeter_greet    (SampleGreeter *greeter);

GObject Introspectionに対応すると、次のPythonスクリプトでこのAPIを使うことができます。

from gi.repository import Sample

greeter = Sample.Greeter()
print(greeter.greet()) # -> Hello!

Rubyスクリプトから使う場合はこうなります。

require "gobject-introspection"

module Sample
  loader = GObjectIntrospection::Loader.new(self)
  loader.load("Sample")
end

greeter = Sample::Greeter.new
puts(greeter.greet) # -> Hello!

どちらの場合も、「Greeterクラスを定義、Greeterクラスにはgreetメソッドを定義」ということを指定していません。「Sampleライブラリーを使う」と指定しているだけです。

SampleライブラリーのAPIは2つの関数しか提供していませんが、通常、ライブラリーのAPIはもっと多くの関数を提供しています。ライブラリーがGObject Introspectionに対応していると、それらの関数をどのように使うかを1つずつ定義しなくてもスクリプトから使えます。便利ですね。

GObject Introspection対応の仕方

それでは、GObject Introspectionに対応する方法を説明します。ビルドシステムにはAutotoolsを使用していることを前提としています。

GObject Introspectionに対応するためには次の2つのファイルを編集します。

  • configure.ac
  • 共有ライブラリーを生成するディレクトリーのMakefile.am

configure.acの変更点は1行です。

diff --git a/configure.ac b/configure.ac
index a75fcbb..d7cb118 100644
--- a/configure.ac
+++ b/configure.ac
@@ -15,6 +15,7 @@ LT_INIT

 AM_PATH_GLIB_2_0([2.32.4], [], [], [gobject])

+GOBJECT_INTROSPECTION_REQUIRE([1.32.1])
 GTK_DOC_CHECK([1.18-2])

 AC_CONFIG_FILES([

GOBJECT_INTROSPECTION_REQUIRE()を追加しているだけです。引数で指定している1.32.1は「少なくとも1.32.1以降のGObject Introspectionが必須」という意味です。最低でもこの環境はサポートしたいと考えている環境に合わせればよいです。なお、1.32.1はDebian wheezyのgobject-introspectionのバージョンです。つまり、この指定はDebian wheezyは最低でもサポートしたいという意味になります。

もし、AM_INIT_AUTOMAKE()foreignを指定していない場合は-Wno-portabilityを指定してください。

diff --git a/configure.ac b/configure.ac
index a75fcbb..8214d6c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7,7 +7,7 @@ AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_SRCDIR([sample/greeter.h])
 AC_CONFIG_HEADERS([config.h])

-AM_INIT_AUTOMAKE([1.13 foreign])
+AM_INIT_AUTOMAKE([1.13 -Wno-portability])
 AM_SILENT_RULES([yes])

 AC_PROG_CC

-Wno-portabilityはポータブルではないことに対する警告を抑制します。どうしてこのオプションが必要かというと、GObject Introspectionが提供するMakefile(後述)がGNU make独自の機能を使っているからです。

共有ライブラリーを生成するディレクトリーのMakefile.amの変更点はそこそこあります。

まず、該当するMakefile.amを確認しましょう。今回は次のようなディレクトリー構成になります。

.
|-- Makefile.am
|-- autogen.sh
|-- configure.ac
|-- doc
|   |...
|   `-- ...
`-- sample
    |-- Makefile.am          変更!
    |-- Sample-1.0.gir       GOject Introspectionが生成!
    |-- Sample-1.0.typelib   GOject Introspectionが生成!
    |-- libsample.la         ビルドして生成!
    |-- greeter.c
    `-- greeter.h

共有ライブラリーlibsample.soの元になるlibsample.laをsample/以下に生成するので、該当するMakefile.amはsample/Makefile.amになります。sample/Makefile.amを編集して次のファイルを生成するようにします。

  • sample/Sample-1.0.gir
  • sample/Sample-1.0.typelib

この2つのうち、スクリプトから読み込んでいるのが.typelibの方です。.typelibは.girから生成します。.girはライブラリーから生成します。

Makefile.amはこのようになります。コメントをつけているところがGObject Introspection固有の記述です。GObject Introspectionが提供するMakefileがルールを提供するので、Makefile.amではマクロを定義するだけです。

# 自動生成する.girと.typelibをmake cleanで削除するため。
CLEANFILES =

AM_CPPFLAGS =					\
	 -I$(top_builddir)			\
	 -I$(top_srcdir)

AM_CFLAGS =					\
	$(GLIB_CFLAGS)

lib_LTLIBRARIES =				\
	libsample.la

libsample_la_LIBADD =				\
	$(GLIB_LIBS)

libsample_la_SOURCES =				\
	greeter.c				\
	greeter.h

# GObject Introspectionが提供するMakefileを取り込む。
# マクロ(変数みたいなもの)を定義しておくと
# それを使って.girと.typelibを生成するルールが
# 定義されている。
# (GNU make依存の書き方をふんだんに使っている。)
-include $(INTROSPECTION_MAKEFILE)

# 生成する.girのリスト。
# 後の方でこのマクロに.girを追加していく。
INTROSPECTION_GIRS =
# .girを生成するg-ir-scannerに渡すコマンドライン引数。
# 同じMakefile.amで複数の.girを作成する場合は
# ここで指定した引数がすべての.gir生成で使われる。
INTROSPECTION_SCANNER_ARGS =
# .typelibを生成するg-ir-compilerに渡すコマンドライン引数。
# 同じMakefile.amで複数の.typelibを作成する場合は
# ここで指定した引数がすべての.typelib生成で使われる。
INTROSPECTION_COMPILER_ARGS =

# GObject Introspectionが見つかったときだけ実行されるブロック。
if HAVE_INTROSPECTION
# Sample-1.0.girの依存関係を指定。
Sample-1.0.gir: libsample.la
# 以降、Sample-1.0.girの「-」と「.」を「_」に変換した
# 「Sample_1_0_gir」をプレフィックスとしてパラメーターを指定する。

# Sample-1.0.girが依存するpkg-configのパッケージ名を指定。
# gobject-2.0は指定しなくてもよい。
# 省略可。
Sample_1_0_gir_PACKAGES =
# Sample-1.0.girが提供するpkg-configのパッケージ名を指定。
# 省略可。
Sample_1_0_gir_EXPORT_PACKAGES = sample
# Sample-1.0.girが依存するGObject Introspectionのパッケージ名を指定。
# GObject-2.0でも指定する。
# 省略可。
Sample_1_0_gir_INCLUDES = GObject-2.0
# ライブラリー(libsample.so)を使ったコードをビルドするために必要な
# フラグを指定。このライブラリーは依存ライブラリーがないので必要ないが
# 他のライブラリーに依存している場合は次のように指定すればだいたい大丈夫。
#   Sample_1_0_gir_CFLAGS = $(AM_CPPFLAGS) $(AM_CFLAGS)
# 省略可。
Sample_1_0_gir_CFLAGS =
# .girが対象とするライブラリーを指定。
# 必須。
# (「_LIBS」の代わりに「_PROGRAM」を指定することが可。)
Sample_1_0_gir_LIBS = libsample.la
# Sample-1.0.girに依存するファイルを指定。
# 必須。
Sample_1_0_gir_FILES = $(libsample_la_SOURCES)
# Sample-1.0.girを作るときにg-ir-scannerに渡すコマンドラインを指定。
# 省略可だが、--identifier-prefixと--symbol-prefixは
# 明示的に指定しておいたほうがよい。省略すると自動で推測するが、
# 推測が外れるとどのAPIも.girに含まれなくなる。
Sample_1_0_gir_SCANNERFLAGS =			\
	--identifier-prefix=Sample		\
	--symbol-prefix=sample
# Sample-1.0.girを生成する.girのリストに追加。
INTROSPECTION_GIRS += Sample-1.0.gir

# .girをインストールするディレクトリーを指定。
# ここは変更する必要はない。
girdir = $(datadir)/gir-1.0
# .girをインストールするという指定。
# ここは変更する必要はない。
gir_DATA = $(INTROSPECTION_GIRS)

# .typelibをインストールするディレクトリーを指定。
# ここは変更する必要はない。
typelibdir = $(libdir)/girepository-1.0
# .typelibをインストールするという指定。GNU make依存の書き方。
# ここは変更する必要はない。
typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)

# make cleanで.girと.typelibを削除。
CLEANFILES +=					\
	$(gir_DATA)				\
	$(typelib_DATA)
endif

これでGObject Introspection対応になりました。使ってみましょう。

ここで作ったライブラリーはGitHubで公開しているのでそれを使います。

% git clone https://github.com/kou/gobject-introspection-sample.git

ビルドしてインストールします。

% cd gobject-introspection-sample
% ./autogen.sh
% ./configure --prefix=/tmp/local
% make
% make install

Pythonから使ってみます。スクリプトはこれです。

from gi.repository import Sample

greeter = Sample.Greeter()
print(greeter.greet())

実行するときはGI_TYPELIB_PATHで/tmp/local/lib/girepository-1.0を指定して、インストールしたばかりのSample-1.0.typelibを見つけられるようにします。LD_LIBRARY_PATHを指定しているのはlibsample.soを見つけるためです。

% GI_TYPELIB_PATH=/tmp/local/lib/girepository-1.0:/usr/lib/girepository-1.0 \
    LD_LIBRARY_PATH=/tmp/local/lib \
    python /tmp/sample.py
Hello!

呼び出せましたね。

まとめ

Cで実装したライブラリーをGObject Introspectionに対応させるとスクリプトから簡単に使えることを説明し、対応方法を説明しました。ここで実装したライブラリーはGitHubで公開しています。ライセンスはCC0 (Public Domain)です。

ここでは、複雑な引数(たとえば出力引数)の場合はどのようにすればよいかについては省略しました。複雑な引数のことについて説明するときにGTK-Docの使い方の説明が活きてくるはずでした。また別の機会ですね。