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の使い方の説明が活きてくるはずでした。また別の機会ですね。