言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Gtk のシグナル

#include <gtk/gtk.h>

static void released(GtkWidget *widget, gpointer data)
{
  g_print("Hello World\n");
  //  g_signal_emit_by_name(widget, "released", 0);

}

static void destroy(GtkWidget *widget, gpointer data)
{
  gtk_main_quit();
}

int main(int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *button;  
  
  gtk_init(&argc, &argv);
  
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  button = gtk_button_new_with_label("Hello World!");
  gtk_container_add(GTK_CONTAINER(window), button);
  gtk_widget_show_all(window);
  
  g_signal_connect(G_OBJECT(button), "released", G_CALLBACK(released), NULL);
  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL);
  
  gtk_main();
  return 0;
}

Gtk のシグナルを調べるのにしょうもないプログラムを書いて実験してみました。ブレークポイントgtk_real_button_released と released に仕込むと、この順番で処理が渡る事が分かりました。

シグナルとは、プログラム中のある場所から一度にたくさんのクロージャ(関数)を呼ぶ為の仕組みです。クロージャの結果はアキュームレータという仕組みでまとめられ、呼び出し元に返ります。呼び出しは同期的にすぐ行われます。二度 g_signal_connect すると呼び出しも二度行われ、クロージャの中で元のシグナルを emit すると無限ループになります。

例えばシグナル released の定義は gtkbutton.c の中で次のようになっています。

  button_signals[RELEASED] =
    g_signal_new (I_("released"), // シグナルの名前
		  G_OBJECT_CLASS_TYPE (object_class), // 関連付けるタイプ(GtkButton)
		  G_SIGNAL_RUN_FIRST, // デフォルトのクロージャをすぐ呼ぶ
		  G_STRUCT_OFFSET (GtkButtonClass, released), // デフォルトクロージャ
		  NULL, NULL, // アキュームレータ
		  _gtk_marshal_VOID__VOID, // よく分からない
		  G_TYPE_NONE, 0); // 引数

シグナル定義では、デフォルトクロージャを指定します。この場合押していたボタンを離す動作がデフォルトクロージャです。G_SIGNAL_RUN_FIRST は呼び出す順序を指定するモードで、ユーザが追加するクロージャの前にデフォルトクロージャを起動する事を指定しています。デフォルトクロージャ自体を変更するには g_signal_override_class_closure() を使うそうです。

まとめ

Gtk の中で、必要以上にシグナルが多用されているように感じるのは、シグナルという単一の仕組みにいくつかの役割が与えられているからのようです。その役割とは。

  • ポリモルフィズムGUI 機構の中で、メソッドによるポリモルフィズムは必ずシグナルとセットで使われます。イベント処理等のインスタンスごとのポリモルフィズムを実現する為だと思います。この場合、シグナルは単なるメソッド呼び出しとして使われます。
  • 依存関係。Observer パターンを実現する為に使われます。例えばテキストウィジェットとスクロールバーは表示範囲を表す同じオブジェクトを監視し、片方を動かせばもう片方も動くようになっています。