Pythonは、グラフィカルなインターフェースをコーディングするのには素晴らしい言語です。実際的なコードをすばやく記述でき、時間のかかるコンパイル作業が不要なことから、インターフェースは数分で開発でき、その後ほどなくして実際に利用できるものにすることができます。この特徴を、ネイティブなライブラリーに簡単にリンクできるというPythonの能力と組み合わせると、素晴らしい環境ができあがります。
GNOMEおよび関連するPython用ライブラリーをラップしたパッケージにgnome-python があります。このパッケージを使えば、GNOMEのコア・アプリケーションとまったく同じ外見や操作方法のアプリケーションをPythonで記述でき、しかもCで記述する場合の数分の1の時間で開発できます。
といっても、Cでコーディングしないことの欠点もあります。GNOMEは大半がCで記述されており、Pythonでウィジェットを使用するには、それをラップする必要があります。これは、ラップ処理の原理を理解している人にとっては、たやすい作業ですが、自動的に実現できる作業ではありませんし、またウィジェットがGNOMEのコア・ライブラリーに含まれていて、少なくとも非常に使いやすいものでなければ、それらのウィジェットがラップされることはありません。Cでコーディングを行う人は、かなり複雑なコードを記述しなければならないかもしれませんが、そうした新しい小道具をすべて最初に利用できるわけです。
しかし、つねにそうでなければならないというわけではありません。ウィジェットをラップする作業は、従来、限られた数少ない人々だけの技だとされてきましたが、実際には、そんなに難しいことではないのです。新しく見つけてきたウィジェットをラップできれば、Pythonプログラムでもそれをすぐに利用できるようになります。
本稿では、CでコーディングされたGObject (GTK+ のすべてのウィジェットおよび関連する数多くのオブジェクトのおおもとの基本クラス) をラップして、Pythonのコードから利用できるようにする方法を説明します。gnome-python バージョン1.99.xは、皆さんのマシンにインストールされているものとします (インストールしていない場合には、参考文献に示してあるリンクを参考にしてください)。パッケージを使用する場合には、開発パッケージをインストールしておいてください。また、Python 2.2とそのヘッダーもインストールしておく必要があります。Make、Python、GTK+ 2、およびある程度のCは理解しているものとします。
今回は、作業の具体的な例として、通知エリアのアイコンを抽象するGTK+ ウィジェットEggTrayIcon をラップします。このライブラリーは、libegg モジュールのGNOME CVSに含まれています。本稿を読み終える頃には、TrayIconオブジェクトを包含するtrayicon というPythonのネイティブなモジュールができているはずです。
まずは、eggtrayicon.cとeggtrayicon.hを入手し (リンクは、稿末の参考文献に示してあります)、新しいディレクトリーに保存してください。このソースは、automake環境で構築することを前提としていますので (今回これについては説明を省略します)、ファイルから#include <config.h> を削除するか、config.hという空のファイルを作成し、空のメイクファイルを作成するかしてください。中身は、作業を進める中で、記述していきます。
このオブジェクトをラップする作業の第1段階は、このオブジェクトのAPIを指定するファイルであるtrayicon.defsを作成することです。定義ファイルは、Scheme系の言語で記述します。小さなインターフェースなら、これらのファイルで簡単に記述できるのですが、大きなインターフェースを記述したり、初心者が記述する場合には、やっかいだったりします。
gnome-python には、h2def というツールが用意されています。このツールは、ヘッダー・ファイルを解析して、おおまかな定義ファイルを作成してくれます。ただし、これは、Cのコードを構文解析するのではなく、正規表現を使用しているだけですので、GObjectが伝統的なフォーマットで記述されていることを前提としており、変わったフォーマットで記述されているCのコードは、正しく解析できない場合があります。
定義ファイルの初期バージョンを作成するために、以下のようにしてh2def を呼び出します。
python /usr/share/pygtk/2.0/codegen/h2def.py eggtrayicon.h > trayicon.defs
h2def.pyを /usrにインストールしていない場合には、それが保存されている場所を指すようにパスを変更する必要があります。
生成された定義ファイルの中身を見てみると、いくつかのことがわかります。クラスEggTrayIcon、コンストラクタ群、さらにsend_message とcancel_message というメソッドが定義されています。このファイルに、明白な間違いはなく、メソッドやフィールドで削除したいものもありませんので、このファイルに編集を加える必要はありません。このファイルは、Python専用のものではなく、他の言語のバインディングからも利用できます。
インターフェースの定義が作成できましたので、次に、粗削りなPythonラッパーの作成にとりかかります。それには、まず、オーバーライド・ファイルを作成します。オーバーライド・ファイルとは、どのヘッダーをインクルードし、モジュールをどんな名前にするか、などをコード・ジェネレーターに指示するためのファイルです。
オーバーライド・ファイルは、%%を使って (lex/yaccのスタイルで)、複数のセクションに区切られます。これらのセクションで、インクルードすべきヘッダー、モジュールの名前、インクルードすべきPythonモジュール、無視すべき関数、および最後に、手作業で記述されるラップ関数があれば、それを定義します。以下は、今回のtrayicon モジュール用のオーバーライド・ファイルの初期バージョンです。
リスト1. trayicon.override
%%
headers
#include <Python.h>
#include "pygobject.h"
#include "eggtrayicon.h"
%%
modulename trayicon
%%
import gtk.Plug as PyGtkPlug_Type
%%
ignore-glob
*_get_type
%%
|
このコードについても、詳しく見てみたいと思います。
-
headers
#include <Python.h>
#include "pygobject.h"
#include "eggtrayicon.h"
これは、ラッパーを構築する際にインクルードすべきヘッダー・ファイルです。Python.hとpygobject.hは、必ずインクルードする必要があります。また、eggtrayicon.hをラップするわけですので、これもインクルードする必要があります。 -
modulename trayicon
modulenameの指定では、どのモジュールにラッパーを含めるかを示します。 -
import gtk.Plug as PyGtkPlug_Type
これは、Pythonでのラッパーのインポートです。名前の付け方に注意してください。コンパイルされるモジュールについては、この約束に従う必要があります。通常は、オブジェクトのスーパークラスをインポートしておけば問題ありません。たとえば、オブジェクトがGObjectから直接継承されたものなら、以下のように指定します。
import gobject.GObject as PyGObject_Type -
ignore-glob
*_get_type
これは、無視すべき関数名のglobパターン (シェル・スタイルの正規表現) です。型コードは、Pythonが処理してくれますので、*_get_type関数は無視します。無視しないと、これらの関数はラップされることになります。
これで、オーバーライド・ファイルができましたので、今度は、これを使って、ラッパーを作成します。gnome-python バインディングには、ラッパーを作成するために簡単に使うことのできる魔法のツールが用意されています。そこで、以下のコードをメイクファイルに追加します。
リスト2. メイクファイルの最初のバージョン
DEFS='pkg-config --variable=defsdir pygtk-2.0'
trayicon.c: trayicon.defs trayicon.override
pygtk-codegen-2.0 --prefix trayicon \
--register $(DEFS)/gdk-types.defs \
--register $(DEFS)/gtk-types.defs \
--override trayicon.override \
trayicon.defs > $@
|
これも、詳しく見てみたいと思います。
-
DEFS='pkg-config --variable=defsdir pygtk-2.0'
DEFSは、Python GTK+ バインディングの定義ファイルを格納してある場所のパスです。 -
trayicon.c: trayicon.defs trayicon.override
生成されるCのコードは、定義ファイルとオーバーライド・ファイルに依存します。 -
pygtk-codegen-2.0 --prefix trayicon\
ここで、コード・ジェネレーターgnome-pythonが呼び出されます。引数のprefixは、生成されるコードの中の変数名に付けるプレフィクスとして使用されます。これは、どんな名前にしてもよいのですが、モジュール名にすれば、シンボル名を統一することができます。 -
--register $(DEFS)/gdk-types.defs\
--register $(DEFS)/gtk-types.defs\
今回のモジュールでは、GLibとGTK+ の型を使用しますので、コード・ジェネレーターに対しても、それらの型をロードするよう指示する必要があります。 -
--override trayicon.override\
この引数で、先に作成したオーバーライド・ファイルをコード・ジェネレーターに渡します。 -
trayicon.defs > $@
コード・ジェネレーターに対するこの最後のオプションで、定義ファイルそのものを指定します。コード・ジェネレーターからの出力は、標準出力に対して行われますので、出力をターゲットのtrayicon.cにリダイレクトしてやります。
次にmake trayicon.c を実行し、生成されたファイルの中身を見てみると、CのコードでEggTrayIcon の各関数がラップされているのがわかります。No ArgType for GdkScreen* という警告は、気にする必要はありません。これで正常です。
見てのとおり、ラップ・コードは、複雑な感じのものになっています。コード・ジェネレーターが、コードを作成してくれることは、ありがたいことです。後で、個々のメソッドを手作業でラップする方法についても説明しますが、その場合も、ラッパーそのものを一から記述するのではなく、ラップ・コードを部分的に修正します。
粗削りのラッパーができあがりましたので、今度は、それを起動する方法を考えます。それには、trayiconmodule.cを作成します。これは、Pythonモジュールにとってのmain() 関数だと考えることができます。このファイルは、(オーバーライド・ファイルと同様) 定型的なコードで、少しだけ修正を加えます。以下が、今回使用するtrayiconmodule.cです。
リスト3. TrayIconモジュールのコード
#include <pygobject.h>
void trayicon_register_classes (PyObject *d);
extern PyMethodDef trayicon_functions[];
DL_EXPORT(void)
inittrayicon(void)
{
PyObject *m, *d;
init_pygobject ();
m = Py_InitModule ("trayicon", trayicon_functions);
d = PyModule_GetDict (m);
trayicon_register_classes (d);
if (PyErr_Occurred ()) {
Py_FatalError ("can't initialise module trayicon");
}
}
|
trayicon という語は、いろいろな出所 (でどころ) のものが使われていますので、ここで、いくつか細かいことを説明しておきたいと思います。inittrayicon という関数の名前やモジュールが初期化されるときに使われる名前は、Pythonモジュールで実際に使われる名前であり、したがって、最終的な共有オブジェクトの名前もPythonモジュールで実際に使用されます。配列trayicon_functions や関数trayicon_register_classes の名前は、コード・ジェネレーターに対して指定した--prefix 引数にしたがって決定されます。先ほど触れたように、このファイルのコーディングがあまりわかりにくくならないように、これらの名前は統一しておくに越したことはありません。
名前の出所はわかりにくくなりがちですが、このCのコードは非常に簡明なものになっています。GObjectとtrayicon モジュールを初期化した後、クラスをPythonに登録しています。
これで部品は揃いましたので、次に共有オブジェクトを作成します。以下のコードをメイクファイルに追加します。
リスト4. メイクファイルへの追加内容
CFLAGS = 'pkg-config --cflags gtk+-2.0 pygtk-2.0' -I/usr/include/python2.2/ -I.
LDFLAGS = 'pkg-config --libs gtk+-2.0 pygtk-2.0'
trayicon.so: trayicon.o eggtrayicon.o trayiconmodule.o
$(CC) $(LDFLAGS) -shared $^ -o $@
|
これについても、1行1行確認していきたいと思います。
-
CFLAGS = 'pkg-config --cflags gtk+-2.0 pygtk-2.0' -I/usr/include/python2.2/ -I.
この行は、Cのコンパイル・フラグを定義しています。pkg-configは、GTK+ とPyGTKのインクルード・パスを指定するためのものです。 -
LDFLAGS = 'pkg-config --libs gtk+-2.0 pygtk-2.0'
この行は、リンカーのフラグを定義しています。ここでも、正しいライブラリー・パスを指定するためにpkg-configが使われています。 -
trayicon.so: trayicon.o eggtrayicon.o trayiconmodule.o
共有オブジェクトは、生成されたコードと、先ほど作成したモジュール・コード、およびEggTrayIconの実装コードを基に構築されます。暗黙のルールで、先に作成した .cファイルから .oファイルが構築されます。 -
$(CC) $(LDFLAGS) -shared $^ -o $@
これで、最終的な共有ライブラリーが構築されます。
ここでmake trayicon.so を実行すると、定義ファイルからCコードが生成され、3つのCのファイルがコンパイルされ、最後にそれらのコードがリンクされるはずです。これで、ネイティブなPythonモジュールの最初のバージョンが完成しました。コンパイルやリンクがうまくいかなかったときは、これまでの手順をもう一度チェックし、最初のほうで出された警告が後々のエラーにつながっていないか確認してください。
trayicon.soができましたので、Pythonプログラムの中で実際に使ってみることにします。まずは、これをロードして、そのメンバーを全部表示させてみるのがよいでしょう。シェルからpython を実行し、対話型インタープリターを起動して、以下のコマンドを入力してください。
リスト5. TrayIconの対話形式のテスト
$ python
Python 2.2.2 (#1, Jan 18 2003, 10:18:59)
[GCC 3.2.2 20030109 (Debian prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pygtk
>>> pygtk.require("2.0")
>>> import trayicon
>>> dir (trayicon)
['TrayIcon', '__doc__', '__file__', '__name__']
|
dir で、このリストと同じ結果が得られたでしょうか。今度は、もっと大きな例で試してみたいと思います。
リスト6. 例題コードHello
#! /usr/bin/python
import pygtk
pygtk.require("2.0")
import gtk
import trayicon
t = trayicon.TrayIcon("MyFirstTrayIcon")
t.add(gtk.Label("Hello"))
t.show_all()
gtk.main()
|
1行ずつ細かく分けて見ていきたいと思います。
-
#! /usr/bin/python
import pygtk
pygtk.require("2.0")
import gtk
import trayicon
ここでは、まずGTK+ のバインディングを要求 (require) し、インポートしてから、今回の新しいモジュールをインポートしています。 -
t = trayicon.TrayIcon("MyFirstTrayIcon")
次にtrayicon.TrayIconのインスタンスを作成します。コンストラクタは、文字列引数を1個とります。アイコンの名前を指定します。 -
t.add(gtk.Label("Hello"))
TrayIconの要素は、GTK+ のコンテナーですので、その中には、どんなものでも追加することができます。ここでは、ラベル・ウィジェットを追加しています。 -
t.show_all()
gtk.main()
ここでは、ウィジェットが表示されるように設定し、GTK+ のメインのイベント・ループを開始しています。
さて、まだGNOME PanelにNotification Areaアプレットを追加していないのなら、追加してください (パネルを右クリックし、Add to Panel (パネルへの追加) -> Utility (ユーティリティー) -> Notification Area (通知エリア) を選択していきます)。テスト・プログラムを実行すると、トレイにHelloが表示されるはずです。きまりましたね。
図1. Helloの例
通知エリアでは、他にどんなことができるのでしょうか。たとえば、プログラムで、通知エリアにメッセージを表示させることができます。このメッセージがどんなふうに表示されるかは、実装内容しだいです。現在のところ、GNOMEのこの通知エリアは、ツールチップを表示するようになっています。send_message() 関数を呼び出すことで、メッセージを送信することができます。APIを少し調べてみると、この関数は、タイムアウト値とメッセージを受け取ることになっていますので、以下のコードでうまくいくはずです。
...
t = trayicon.TrayIcon("test")
...
t.send_message(1000, "My First Message")
|
しかし、これではうまくいきません。Cのプロトタイプはsend_message(int timeout, char* message, int length) となっていますので、PythonのAPIでも、文字ポインターと長さを指定する必要があります。以下のコードだと、うまくいきます。
...
t = trayicon.TrayIcon("test")
...
message = "My First Message"
t.send_message(1000, message, len(message))
|
しかし、これでは、少し見苦しくなってしまいます。これはPythonです。プログラミングは簡潔でなければなりません。こんな調子でやっていくのであれば、セミコロンがないだけのCになってしまいます。幸い、コード・ジェネレーターgnome-python を使う場合、個々のメソッドを手作業でラップできるようになっています。
これまでの時点で、われわれが手に入れたのは、send_message(int timeout, char *message, int length) 関数でした。そこで、EggTrayIcon のPython APIとしてsend_message(timeout, message) という呼び出しができるようになればよいわけです。幸い、これは、さほど難しいことではありません。
それには、もう一度trayicon.overrideに手を加えます。これは、ファイルの名前から察しのつくことです。このファイルは、基本的に、手作業でオーバーライドしたラッパー関数を入れておくためのものなのです。仕組みを説明するのは、サンプル・コードを示して中身を追っていくよりもずっと大変ですので、ここでは、手作業でラップしたsend_message のコードを示すことにします。
リスト7. 手作業でのオーバーライド
override egg_tray_icon_send_message kwargs
static PyObject*
_wrap_egg_tray_icon_send_message(PyGObject *self,
PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {"timeout", "message", NULL}; int timeout, len, ret;
char *message;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "is#:TrayIcon.send_message", kwlist,
&timeout, &message, &len))
return NULL;
ret = egg_tray_icon_send_message(EGG_TRAY_ICON(self->obj),
timeout, message, len);
return PyInt_FromLong(ret);
}
|
ここでも、コードを1行1行分解して、説明したいと思います。
-
override egg_tray_icon_send_message kwargs
この行では、egg_tray_icon_send_messageを手作業で定義するので、それを生成しないでほしいということをコード・ジェネレーターに指示しています。 -
static PyObject*
_wrap_egg_tray_icon_send_message(PyGObject *self,
PyObject *args, PyObject *kwargs)
これは、PythonからCへのブリッジのプロトタイプです。プロトタイプには、メソッドの呼び出し先であるGObjectへのポインター、引数の配列、キーワード引数の配列が使われています。Pythonの値は (整数も含め) すべてオブジェクトですので、戻り値は、つねにPyObject*です。 -
{
static char *kwlist[] = {"timeout", "message", NULL};
int timeout, len, ret;
char *message;
この配列は、この関数で解釈可能なキーワード引数の名前を定義します。必ずキーワード引数を使用できるようにしなければならないというわけではありませんが、引数をたくさん使用するコードを非常に明快なものにすることができますし、大した手間もかかりません。 -
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
"is#:TrayIcon.send_message", kwlist,
&timeout, &message, &len))
return NULL;
この入り交じった感じの関数呼び出しでは、引数の解析を行っています。引数に、解釈可能なキーワード引数のリストと渡されてきたすべての引数を渡すと、最終的な引数が値を指すように設定します。暗号めいた文字列は、関数がとるべき変数の型を宣言したもので、これについては後で説明します。 -
ret = egg_tray_icon_send_message(EGG_TRAY_ICON(self->obj),
timeout, message, len);
return PyInt_FromLong(ret);
}
ここでは、実際にegg_tray_icon_send_messageを呼び出し、返されてきたintをPyObjectに変換しています。
最初は、少し手ごわそうにみえますが、まず、trayicon.cに生成されたコードをコピーしてきます。関数がとるべきパラメーターを部分的に修正したいだけであれば、たいていは、これでまったく問題ありません。生成されたCから変更したい関数をコピー・アンド・ペーストしてきて、魔法のオーバーライド行を追加し、望みどおりの動作になるまでコードを編集すればよいだけのことです。
最も重要な変更内容は、関数がとるべきパラメーターを変更することです。PyArg_ParseTupleAndKeywords 関数に指定されている何かわけのわからなそうな文字列は、関数がとるべき引数を定義しています。これは、元の形はisi:TrayIcon.send_message で、パラメーターがint、char* (文字列としてはs )、intであるという意味です。そして、例外がスローされると、TrayIcon.send_message という名前で関数が呼び出されることを意味しています。Pythonのコードでは文字列の長さを指定しなくてもよいようにしたいわけですので、isi をis# に変更します。s ではなくs# としているのは、PyArg_ParseTupleAndKeywords が文字列の長さを自動的に計算して、別の変数にセットしてくれる (まさにわれわれがやりたいと思っていたこと) という意味です。
新しいラッパーを使用するには、共有オブジェクトを構築し直し、テスト・プログラムのsend_message 呼び出しを、以下のように変更します。
t.send_message(1000, message) |
すべて計画どおりになっていれば、修正後のコードも同じ動作をするはずです。しかもすっきりしたコードになりました。
今回は、小さいながらも便利なCのGObjectを取り上げ、それをラップしてPythonで利用できるようにしました。さらに、必要に合わせて、ラッパーに手作業での修正も加えました。今回紹介した手法は、いろいろななオブジェクトに数多く適用できますので、見かけたGObjectはどんなものでもPythonから利用できるようにすることができます。
- eggtrayicon.cや開発に使ったサンプル・コードなど、本稿で説明したソースのtarball は、ダウンロードできます。
-
PythonとDB2 for Linux を利用すれば、汎用のプログラミング言語を必要とするような複雑な行うことができます。
-
The Camel and the Snake は、Perl、Python、DB2といったオープン・ソース・ソフトウェアによる開発を解説しています。
- developerWorks のLinuxゾーンには、その他、LinuxやPythonの開発者向けの参考文献が数多く掲載されています。
Ross Burton は、昼間はJavaと組み込みシステムをコーディングする凡庸なコンピューターサイエンス学士取得者です。その恐怖から逃避するために、夜な夜なPython、C、そしてGTK+に浮気をします。r.burton@180sw.comでRossとコンタクトできます。