イベント駆動


Table of Contents

1. イベント駆動
イベント駆動への道
強制終了
処理の段階の間でのユーザ入力
二つの入力
同時に複数の処理
サーバ
イベント駆動
イベント駆動の実際
イベント待ち関数を自分で呼ぶ
イベントハンドラを登録する
ウィジェット・クラス

Chapter 1. イベント駆動

イベント駆動への道

強制終了

ストレートに処理をするプログラムを作ったとします。さて、処理中にユーザ が中止させようとしたらどうします。無視します?多分プログラムとしては無 視するように書く事はできます。Contorl-C を無視するくらいはできます。 Window が閉じるのを阻止する事もできます。

そうしたら多分ユーザは強行手段に出ます。OSに強制終了を要請するでしょう。 タスクマネージャを起動してプロセスの終了とか、kill -9 とか。

OSに強制終了させられた場合、処理の途中のテンポラリファイルとかサーバと の接続とかどうなります?。そのまま中途半端なデータが残るか、ファイルが 壊れてしまうか。下手すりゃディスクのフォーマットからOSの再インストール まであり得ます。

処理の段階の間でのユーザ入力

処理の途中でユーザから追加のデータの入力を求めたり、次の処理の選択を求 めたりする必要が生じるようなプログラムを作る事もあるでしょう。

ユーザにプロンプトを提示してコマンド、数値、ファイル名等を入力を即しま す。ウィンドウズのウィザードはこれの例ですが、昔のプログラムはみなこん な感じした。

ユーザがこちらの求めること以外をしたがったらどうなるでしょう。キャンセ ルボタン位は付けましょう。強制終了されるのは嫌ですからね。

ユーザ入力 処理 ユーザ入力 処理 ....

の繰り返しをするわけです。

二つの入力

いわゆるターミナル(端末)と呼ばれるプログラムを使ったり作ったりした事が ありますか?昔はネットワークと言えばXXX-Serveなどのパソコン通信の事を さしたものです。ここでは、ターミナル・プログラムが基本の道具です。

ターミナルプログラムは同時に二つの入力を待つことになります。ユーザから のキーボード入力と通信です。もちろん、

ユーザ入力 送信 データ受信 画面出力 ユーザ入力 ....

というループで書いても良いですが、ユーザ入力中や送信中にホストから通信 が来るかも知れません。

ユーザからのキー or データ受信 - 送信 or 画面出力

となります。ユーザからのキー入力やデータ受信が何らかのシーケンスだった ら、すぐに送信したり画面出力したりできません。シーケンスの完了を待つ必 要があります。

同時に複数の処理

例えば、ワープロとかエディタのプログラムを作るとしましょう。同時に一つの文書しか編集できないのであれば

キー入力 表示

のループは単純で良いですが、同時に複数の文書を編集できるようにすると、 キー入力は、文字の入力だけでなく、文書の切替え等のコマンドを含むように なります。

サーバ

サーバのプログラムは複数のクライアントからのアクセスを受けます。

イベント駆動

結局、ユーザとの複雑な対話や通信するGUIプログラム、そして複数のクライ アントからのアクセスを受けるサーバプログラムは、外部からの入力に随時反 応するように作る必要があります。

随時というのはユーザのマウスが動いたとか、キーを押した(押して離したじゃ やくて)とか、1バイト受信したとか、本当に小さな(些細な)事象にも反応し なければならないという事です。そんなコードを自分で書いていたら頭が変に なります。

共通の道具や枠組を作っておいた方が便利ですし、逆にそれを使う事を強要す る事で、複数のプログラムが協調して(あるいは、相互に妨害せずに)動けるよ うになりますし、プログラムの動き方の特性も一定になりますから、ユーザに とっても便利になります。

と言うことで、GUIプログラムやサーバプログラムはイベント駆動型の構造を しています。

イベント駆動型は同時に複数の仕事をこなす為の技術でもあります。実は、マ ルチスレッド技術がもっと早く普及していたら、イベント駆動型は無かったか 今とは大分違った形をしていたかも知れません。

イベント駆動の実際

イベント駆動型の枠組では必ずイベント待ち関数が存在します。プログラムを 書く時にそれを直接呼ぶようなもの(MacOS Classic, X lib、Win32 API)と直 接は呼べないもの(MFC, Java/AWT, Java/Swing, Gtk)があります。

イベント待ち関数を自分で呼ぶ

MacOS Classic, X lib, Win32 API で GUI アプリケーションをゼロから書く 人はもういないでしょう。これらを直接触るのは、ツールキット(MFC, Java, Gtk等)を作る人だけです。それでも、一応どんな仕組みになっているかぐらい は知っておいても無駄ではありません。

/*
  いにしえの Mac OS のイベントループ
  WaitNextEvent() は OS の API
  handleXXX() は自分で書く。
*/

void main()
{

   /* 前略 */

    while (true) {

        struct Event event;
        if (WaitNextEvent(&event)) {

            switch(event.what) {

            case mouseUp:
                handleMouseUp(&event);
                break;

            case mouseDown:
                handleMouseDown(&event);
                break;

            case keyDown:
                handleKeyDown(&event);
                break;

            case keyUp:
                handleKeyUp(&event);
                break;
            }
        } else {
            handleIdle();
        }
    }
}

static handleMouseDown(struct Event* evp)
{
    Window* pWin;
    short part = FindWindow(evp->where, &pWin); /* どのウィンドウの何処にマウスが落ちたか調べる API */

    ...
}

昔はこんな感じで GUI を書いていました(懐かしい...)。ちょっとしたプログ ラムでも1000行はすぐに越えます。もっとも、毎回似たようなコードを書 いているのでコピペですが。これで、ラバーバンドやドラッグドロップを実装 するのがどんなに大変か想像できます?

こういう枠組では、自分が作った Window の一覧や、最後にクリックした座標 等を記録して置かなければなりません。グローバル変数が山のように増えます。

この枠組の欠点は何と言っても、コードの量が多くなり、状態を記録する変数 も沢山必要になります。一方、利点が無いわけではありません。それは、 WaitNextEvent() から出て来て、もう一度それを呼ぶまでのプログラムの流れ を完全に自分で制御できる事です。これができるかどうかが、プログラマとし てのレベル2から3へのギャップでしょう。

イベントハンドラを登録する

さすがに、前者の仕組みしかないと、誰も GUI アプリケーションが書けない ので、各社はいろいろ考えました。そこで、GUI の構造(窓やプログラム)に枠 組を設けてその中でプログラマには生きてもらう事にしました。

ツールキットが登録された関数(イベントハンドラ)をイベントに応じて呼び出 すというタイプです。


/* Gtk の例 */

int main(int argc, char **argv)
{
  gtk_init(&argc, &argv);    /* Gtk ライブラリの初期化 */

  GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);  /* 窓を作る */

  g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL); /* 窓を閉じるボタンへの対応 */

  /*
    窓の中身を構築する。

      - widget を作る
      - window に組み込む
      - ハンドラを登録する
    を繰り返す
  */

  gtk_widget_show(window); /* 窓を見せる */

  gtk_main();

  return 0;
}


オープンソースの人は C++ が嫌いな人が多いので Gtk も C で作られていま す。設計的としてはオブジェクト指向なのですが、構文の支援が受けられない のでコールバックというやり方を使っています。

Web ブラウザの中で動く Dynamic HTML の JavaScript でもイベントハンドラ 式が使われています。

イベントハンドラの問題点のひとつとして、イベントハンドラ関数がどのクラ スのメンバにも属していないことです。無理に書けば、


class MyButton {

    void handleClick(const Event* evp)
    {
        /* ボタンのクリックに反応する */
    }

public:
    static click_handler(const Event* evp, void* user_data)
    {
        MyButton* self = (MyButton*)user_data;
        self->handleClick(evp);
    }
};

int main(int argc, char** argv)
{

    MyButton my_button;

    Button* button = new Button();

    Button->register_click_handler(MyButton::click_handler, (void*)&my_button);

}

してと結びつけられます。あまりスマートとは言えないです。

ウィジェット・クラス

オブジェクト指向オタクというのは、クラスの精密さとか一貫性とかの追求し はじめるとと留まる所を知りません。そうなると、イベントハンドラ自体をク ラスのメソッドにできるような仕組みを考えることになります。

Java/AWT や Java/Swing、MFC 等のクラスライブラリでは、メソッドのオーバー ライドによってイベントハンドラを実現しています。