プログラミング

A sample to create a GObject class (2).

4. クラスのヘッダを記述する
C++ オブジェクトのクラス Stack に対応する構造体を定義し、必要なマクロをヘッダ・ファイル sample-stack.h の中に定義する。ここで参考にするのは、ヘッダ・コードでお決まりの部分にあるサンプルコード。これをテンプレートにして記述するのが簡単。
まず通常のC言語のヘッダ・ファイル同様に:

#include SAMPLE_STACK_H
#define SAMPLE_STACK_H

で始めて、次で終わる:

#endif  /* SAMPLE_STACK_H */

次に、クラスを定義するのに必要な他のヘッダを取り込むわけだけども、GObject による実装で必須なのは、次のヘッダ・ファイル:

#include <glib-object.h>

型管理システムやシグナル管理システム、プロパティ管理システムなどを提供するクラス構造体 GObject を継承するために必要なファイル。
そしてクラス構造体とインスタンス構造体を定義するわけだけども、これらの構造体をクラスぽく扱えるようにするためのマクロを六つ定義しておく。お約束ごとに従うだけ:

#define SAMPLE_TYPE_STACK            (sample_stack_get_type ())
#define SAMPLE_STACK(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SAMPLE_TYPE_STACK, SampleStack))
#define SAMPLE_STACK_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SAMPLE_TYPE_STACK, SampleStackClass))
#define SAMPLE_IS_STACK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SAMPLE_TYPE_STACK))
#define SAMPLE_IS_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SAMPLE_TYPE_STACK))
#define SAMPLE_STACK_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SAMPLE_TYPE_STACK, SampleStackClass))

テンプレートを参考にアプリケーション名とクラス名に置き換えるだけ。作成したクラスをコンパイルするときに妙なコンパイル・エラーが発生するなら、このマクロを確認する。たとえば、ホワイト・スペースが必要な場所に正しく挿入しているか、アプリケーション名とクラス名を逆にしていないかなど確認する。まぁ、こんなマクロを記述するくらいなら、C++ で記述した方がマシと思うかもしれないけど。
次に、これから定義する二つの構造体の型マクロを定義しておく:

typedef struct _SampleStack SampleStack;
typedef struct _SampleStackClass SampleStackClass;
typedef struct  _SampleStackPrivate SampleStackPrivate;

上がインスタンス構造体で、下がクラス構造体。一個余計な型マクロがあるけれども、これは Private なメンバを定義する構造体のためのもの。このヘッダの中では定義せず、クラスのソース・コードで定義することに注意する。
で、いよいよインスタンス構造体とクラス構造体を定義する。ここで説明されているとおり、インスタンス構造体は GObject parent、クラス構造体は GObjectClass parent; が必須でお決まりのコード。ちなみに、これらのメンバは共に Private な扱いになる:

struct _SampleStack {
/*< private >*/
GObject parent;
SampleStackPrivate *_private; };
struct _SampleStackClass { /*< private >*/ GObjectClass object_class;
/* Class Members */ void (*initStack) (SampleStack *self); void (*push) (SampleStack *self, const char c); char (*pop) (SampleStack *self); };

とりあえず、クラスのメソッドは無視することにして、重要なポイントが一つ。メソッドを除く Private なメンバはクラス構造体ではなく、インスタンス構造体に定義するということ。なるべくコメント /*< private >*/ を付与して分かりやすくする。Private なメソッドはクラスの初期化/終了処理で記述するので。このあたりは ここの後半部分で説明されている。
ふm。このあたりから C++ のオブジェクト指向的な考えがぶっ飛んでしまうところだけども、詳細はこのあたりで説明されている。
最後に、クラス構造体の中でメソッドを定義する:

struct _SampleStackClass {
/*< private >*/
GObjectClass object_class;
/* Class Members */ void (*initStack) (SampleStack *self); void (*push) (SampleStack *self, const char c); char (*pop) (SampleStack *self); };

クラスのメソッドは単なる関数へのポインタ。ポイントはインスタンス SampleClass *self を第一引数にとること。実装はクラスのソースコードに記述する。三つのメソッドが全て Public なのでプロトタイプ宣言することも忘れずに。関数名や他の引数は C++ のクラス Stack のメソッド同様である。そしてヘッダの作成の締めに、クラス SampleStackClass を型システムへ登録する関数 GType sample_stack_get_type (void); も宣言しておく。その実装はクラスのソース・コードで:

GType sample_stack_get_type (void);
void sample_stack_initStack (SampleStack *self); void sample_stack_push (SampleStack *self, const char c); char sample_stack_pop (SampleStack *self);

実際にメソッドを呼び出す際は、名前衝突を避けるためにアプリケーション名とクラス名を指定し、sample_stack_pop() というスタイルで呼び出すことになるので、ここで宣言するメソッドの関数にも sample_stack_ を付与してエキスポートしていることに注意。
なんとかクラスぽい形になっているけど、やはり GObject のメカニズムが足かせになっているのか、C++ 的なクラスの姿は全くない。
クラス SampleStack を型管理システムへ登録する部分、Private なメンバの定義と、これを用いたメソッドの実装については、また明日にでも。

A sample to create a GObject class.

とある C++ プログラマの方から Glib Object システム v0.10.0 (日本語版) を読んだけど、GObject を使ったクラスの書き方が今ひとつ分からない、という質問を頂いた。このドキュメントに書いてある手順でコードを記述したが gcc のコンパイル・エラーがたくさん出てしまうらしい。
Private なメソッドのチェックなど、C++ のオブジェクト指向を考慮した g++ と異なり、gcc はクラス (型) のチェックをしてくれる訳ではないので妙なコンパイル・エラーに見えるのだろうが、C プログラマにとってはごく当たり前な Syntax エラーだと思われる。
ということで、簡単な C++ オブジェクトのクラスと、それを GObject で置き換える手順について少しだけまとめてみた。サンプルとした C++ のクラスは、詳説C++ 完全理解の P28 にあるスタックアルゴリズムで、スタックの構造を極めてシンプルに実装したクラスだ:

#include <iostream.h>  //単純なスタック
using namespace std;
#define STACK_SIZE (100)
class Stack { int sp; char buff[STACK_SIZE];
public: void initStack() { sp = 0; }
void push (const char c) { if (sp >= STACK_SIZE) { cout << endl << "error!" << endl; } buf[sp++] = c; }
char pop() { if (sp <=0 ) { cout << endl << "error!" << endl; } return buff[--sp]; } };
int main () { Stack s1;
s1.initStack(); s1.push ('p'); s1.push ('e'); s1.push ('e'); s1.push ('k'); cout << s1.pop (); cout << s1.pop();
// s1.sp = 1; /* 不正操作は検出される */
cout << s1.pop (); cout << s1.pop();
return 0; }

上記のクラス Stack は固定サイズのバッファと、スタックの現在位置を示すポインタを Private なメンバとしてカプセル化している。これらのメンバを操作する Public なメンバとして三つのメソッドが提供されている。main() の中で、実際にクラス Stack のインスタンス s1 を生成し、スタック・ポインタを初期化してから、スタックに四文字格納し、順に取り出している。途中、コメントアウトしている行が Private なメンバに直接アクセスしようとしているが、g++ では当然ながらコンパイル・エラーになる。
このクラスをこのドキュメントで紹介しているポイントに従ってコードを作成する訳だけども、コードの記述方法など余計なポイントがいろいろ混ざっているし、そのポイント自体が別のセクションに散在してしまっているので、確かに理解しにくい構成だ。おまけに、サンプルとして掲載されているクラスも不可解な名前だし。
上記のように純粋なクラスを GObject を使って記述するだけなら、次のような手順になる:

  1. アプリケーション名を決める (接頭子)
  2. クラス名を決める (オブジェクト名)
  3. コードのファイル名を決定して Makefile を作る
  4. クラスのヘッダを記述する
    1. お約束ごとに従い、お決まりのマクロを記述する
    2. 型とクラスの型をお決まりの typedef で定義する
    3. インスタンス用構造体とクラス構造体を記述する
    4. 型登録に必要な関数を宣言する
    5. メソッドを宣言する
  5. クラスのソースを記述する
    1. プライベートなメンバを定義する
    2. 型を登録する関数 *_get_type () を記述する
    3. インスタンスの初期化関数とクラスの初期化関数を記述する
    4. メソッドを実装する
  6. コンパイルしてみる
  7. main() を実装して、クラスとリンクし実行してみる

もちろん、このドキュメントで紹介しているように、プロパティを持たせたり、インタフェースやシグナルを独自に実装する場合は、さらに追加の手順が必要になるけど、GTK+ のようなオブジェクト指向のツールキットや Nautilus などのように独自にシグナルを提供する巨大なアプリを作成するとか、GObject が提供する機能を全て使いたい云々を除けば、この手順で大方まかなえると思う。
1. アプリケーション名を決める
2. クラス名を決める
今回は単なるサンプルということで、アプリケーション名は Sample、クラス名はそのままの Stack にする。ここで決めた名前が、以降で記述するファイル名やマクロの名前に反映されることになる。
3. コードのファイル名を決定して Makefile を作る
クラスのヘッダとソースに使用する名前は、アプリケーション名とクラス名を ‘-‘ で連結して、それぞれ sample-stack.hsample-stack.c にした。アプリケーション名とクラスのファイル名が決まったので、Makefile も作成しておく。コードを記述しながら随時 make して確認できるので。といっても、本格的なものではなく、次のような簡易版でいいだろう:

export CFLAGS += `pkg-config --cflags gobject-2.0`
export LDFLAGS += `pkg-config --libs gobject-2.0`
export CFLAGS += -g
sample: sample sample-stack.o
clean: $(RM) *.o *~ sample

ふm。ちょっと眠くなったので、この先はまた明日にでも。
ちなみに、このメールによれば、Glib Object システム のオリジナル版が、正式に CVS へ追加されることになったようだ。以前、指摘した間違いやスペル間違いとかは直っていない…face-crying.png

How to Write a Basic Gtk# Program with Mono.

部屋を掃除していたら、以前印刷した O’REILLY ONLamp.com に掲載された “How to Write a Basic Gtk# Program with Mono” の紙切れが出てきたので、とりあえず翻訳してみた:
Mono を使った、基本的な Gtk# プログラムの書き方
編集者のメモ: O’Reilly の最新刊 Mono: A Developer’s Notebook には、ざっと 50 近い mini-projects が掲載されており、バージョン 1.0 リリースにおいて最も重要な点やある程度やむを得ない制限事項などが紹介されています。この本では、Windows 上に Mono をインストールする手順や、Gtk# がどのように動くのか、そしてシンプルな Gtk# プログラムの書き方などが記述されており、まさに mini-projects を並べて紹介しているような構成になっています。
Gtk# は、GTK+ ユーザ・インタフェース・ツールキットに対する Mono API です。対して、Mono では Windows.Forms も実装しています。Gtk# は完全なオープンソースな代替物であり、Linux 上で Mono のプログラムを実行するためのルック&フィールの選択肢です。このラボでは Mono を Windows にインストールする手順とシンプルな Gtk# プログラムの書き方について実例をまじえて説明しています。
ユーザ・インタフェースを構築するツールキットでは、ユーザからプログラムへイベントを配送する方法を定義しています。プログラマの指示に従ってプログラムを制御する非対話式プログラムに対して、対話式プログラムではプログラムで受け取るイベントに反応しなければなりません。
Gtk# では、プログラムを制御する流れはメイン・イベント・ループによって握られています。プログラムは自分の制御をメイン・ループへ譲って入力イベントを処理させます。イベントが発生したときはいつでも、それらのイベント・ハンドラが呼び出され、メイン・プログラムへ制御を渡します。

Mono は Common Language Runtime (共通言語ランタイム) のオープンソースな実装です。
Gtk# ユーザ・インタフェース・ライブラリを含んでいます。

Mono: A Developer's Notebook

関連図書:

Mono: A Developer’s Notebook
共著: Edd DumbillNiel M. Bornstein


どうすれば良いのか?
最初に、お使いのコンピュータに Mono と Gtk# をインストールして下さい。Mono の Windows 版インストーラは http://go-mono.com/download.html からダウンロードできます。Gtk# の Windows 版インストーラは http://gtk-sharp.sourceforge.net/ にあるリンクをたどるとあります。
Mono は未だ積極的に開発されているので、問題が定期的に発生します。http://www.nullenvoid.com/gtksharp/wiki/index.php/InstallingBeginnersGuideForWindows にある Gtk# Windows 版ビギナーズ・ガイドを確認し、ダウンロードした Gtk# のバージョンに応じて与えられた指示に従って下さい。

お使いのコンピュータの C:\Windows フォルダにある、
Visual C++ のランタイム msvcr70.dll を確認しておいて下さい。
これは Microsoft Ⓡ のサイトから自由にダウンロードできます。

次にインストールした Mono を格納したサブ・フォルダ (たとえば C:\Program Files\Mono-0.31\bin) を参照するよう、環境変数 MONO_PATH をセットします。さらに環境変数 PATH にも、このフォルダを追加しておいてください。これで Mono のインストールが完了しました。それでは、ウィンドウを一つ、ボタンを一つ持った基本的な #Gtk# プログラムを構築することにしましょう。このウィジットは共にイベントを発行して、プログラマが処理したい内容を記述したハンドラを追加してみます。
サンプル2: 基本的な Gtk# アプリケーション: 01-basics/Main.cs

// 04-gtk/01-basics
using System;
using Gtk;
class MainClass {
public static void Main (string[] args)
{
Application.Init ();
Window w = new Window ("Gtk# Basics");
Button b = new Button ("Hit me");
w.DeleteEvent += new DeleteEventHandler (Window_Delete);
b.Clicked += new EventHandler (Button_Clicked);
// GUI の初期化
w.Add (b);
w.SetDefaultSize (200, 100);
w.ShowAll ();
Application.Run ();
}
static void Window_Delete (object o,
DeleteEventArgs args)
{
Application.Quit ();
args.RetVal = true;
}
static void Button_Clicked (object o,
EventArgs args)
{
System.Console.WriteLine ("Hello, World!");
}
}

通常のテキスト・エディタを使って、ソース・コードを Main.cs として保存して、次のようにコンパイルします:

% mcs Main.cs -pkg:gtk-sharp
Compilation succeeded
% mono Main.ext
-r:gtk-sharp は Gtk# アッセンブリを参照するよう指示するコンパイラ・オプションです。
このオプションを省略すると、コンパイラは Gtk クラスがどこにあるのか分からなくなります。

コンパイルが完了したアプリケーションを実行すると、図8にあるようなウィンドウを見ることができるでしょう: ボタンはウィンドウの余白一杯に広がって配置されています。ボタンを数回クリックすると、図9のように端末上に表示される出力を見ることができます。
Main_cs.png
図8. ボタンが付いた Gtk# ウィンドウ
Main_cs_out.png
図9. 端末の出力
どのように動いているのか?
Gtk の名前空間は Gtk を使って実装されています。ツールキットを初期化する基本的な Gtk# の処理はアプリケーション・クラスが実行しています。特に、興味を引く三つのスタティックなメソッドがあります。

  • Init() は Gtk# ライブラリを初期化します。これを行わない限り何も始まりません。
  • Run() は制御の流れをメインのイベント・ループに移します。
  • Quit() は制御が返ってきたらイベント・ループを終了します。
あなたのアプリケーションで、グラフィックスを利用することが可能かどうかを
チェックさせたい場合は、Init() メソッドのかわりに InitCheck() メソッドを
利用してみてください。この方法だと、ひどい失敗を起こさずにテキスト・インタフェース
へフォールバックすることができます。

ウィンドウとボタンの生成に関しては、それほど大げさなものではないです: もっと興味深いサンプルが他のラボにありますから。ここでもっとも重要なことは、イベント・ハンドラのフッキングです。Gtk# の各ウィジット (ユーザ・インタフェースの要素) は、いろいろなイベントを発行することが可能です。それらの多くは、Gtk# ウィジットの全ての派生元である、基本ウィジット・クラスから配送されています。フォーカスを含め基本的なイベントは、ウィジットがユーザのフォーカスを受け取ると発行され、DeleteEvent はウィジットが削除されたら発行されます。
イベントが発生すると、それに対応したハンドラが呼び出されます。イベントには、いくつかのハンドラを割り当てることが可能です。サンプル2では、ハンドラ Window_Delete をイベント DeleteEvent に追加しています。

イベント・ハンドラが一個のオブジェクト・インスタンスにアクセスする場合、
スタティックなメソッドである必要はありません。
インスタンスによって prefixed されるハンドラの割り当てを行うことだけが
違います。

ここで取り上げたハンドラ Window_Delete は興味深い二つの処理を行います。一番目は、制御が戻ってきたらメイン・ループを終了させます。二番目に、オブジェクト args を介して返り値として ture を返しています。これは、これ以降にイベントを受け取ったハンドラの処理を停止させます。
ボタンのハンドラもまったく同様で、汎用ウィジットのイベント・ハンドラそのものです。ボタンは特殊なイベントをいくつか持っており、その一つである Clicked は一番便利です。
ハンドラの割り当てについては、その下に横たわる実装へのヒントを与えることで完全なものになります。しかしながら、ショートカットを用いることも可能です。これは Mono で実装されている C# 2.0 の機能 “anonymous methods” からきており、次のように二行記述します:

w.DeleteEvent += Window_Delete;
b.Clicked += Button_Clicked;

さらなる情報…
Gtk# を使ったプログラミングの紹介は Monodoc と一緒に提供されているドキュメントの一部としてご覧になることが可能です。Monodoc の目次から、”Mono Handbook” にある “Mono for Gnome Application” のセクションを参照してみて下さい。同様に、すばらしいリファレンス・マテリアルのソースが Monodoc にある “Gtk# API Reference” です。http://go-mono.com/download.html から Monodoc をダウンロードするか、あるいは http://go-mono.com:8080/ にあるオンライン・ドキュメントをご覧になれます。
delegate (委譲) とイベントについて、もっと理解したい場合は、”C# Essentials, 2nd Editon” (Ben Albahari/Peter Drayton/Brad Merrill 共著、O’Reilly, 2002) の第二章を参照してみてください。GTK+ ツールキットのすばらしい紹介は、GTK+ 2.0 チュートリアル でご覧いただけます。GTK+ のC言語の実装について紹介してありますが、この本でさわりだけ紹介したツールキットに関するたくさんの理論が含まれています。
C# 2.0 における変更点についての概要は、http://msdn.microsoft.com/vcsharp/team/language/default.aspx にある Microsoft の C# ウェブサイトのリンクからたどることができます。