Ruby言語やLinuxのネタが多いです。
August 08, 2005 [おもひで]
■ [Ruby-GNOME2] WikiRPC Client by ZnZさん
今のところHiki専用だそうです。Ruby/GTK2, Ruby/Libglade2を使って頂いてます。
■ [Ruby-GNOME2] 大バグ発見
元ネタはこちら。実はこれ、昔から気づいてたんだけど今まで放置しちゃったんだよなぁ。てへっ。
とまぁ、ちょっと頭を整理するためにここにメモしてみる。
まず、以下のPureRubyWindow, GtkWindowという仮想的なクラスを例としてあげる。
(1)
class PureRubyWindow
def child
@child
end
def set_child(value)
@child = value
self
end
end
(2)
class GtkWindow
def child
gtk_widget_get_child(self) #実際のCのコードを呼び出すイメージ
end
def set_child(val)
gtk_widget_set_child(self, val)
#=> valはGTKの内部でCのポインタとしてchildが管理されている。
self
end
end
この例で、以下のようなコードを書いたとする。Ruby-GNOME2ではよく見るコードだと思う。
def create_window child = Button.new window = PureRubyWindow.new # window = GtkWindow.new window.set_child(child) end window = create_widget
このコードではchildの有効な生存期間はwindowの生存期間と同じ、となるのが直感的だ。実際にウインドウが表示されているときに、自分が生成したボタンのインスタンスが実は無くなっているっていうのは気持ち悪いよね。つまり使用者はPureRubyWindowみたいな動作を期待するはず。
が、しかし。
GtkWindow版では、それぞれCのAPIを呼び出すだけになっており、実体としてCのライブラリ側ではchildは生き続けるが、Rubyオブジェクトとしてcreate_windowが呼び出された後はGCの対象になってしまう(はず)。
■ で、実は現在のRuby-GNOME2の実装は上記のGtkWindowになっている。
GtkWindow#childが返す値はC言語のポインタとしては同じものを指しているものの、それをラップするRubyオブジェクトが毎回違う、ということが発生する(GCのタイミングにもよるみたい)。
もうちょっと厳密に言うと、一度Rubyオブジェクト→Gtkオブジェクトに変換される際にGtkオブジェクト自身にRubyオブジェクトのポインタを覚えさせることをしているので上記のcreate_windowのような順番では問題ないのけど、それは話の本筋と異なるので割愛する。
■ これにより問題となるのは、GtkWindow自体を自分で拡張したりサブクラスを作成したときにRuby側で定義したオブジェクト内の情報(インスタンス変数や特殊メソッド等)が正しく引き継げないことだ(Rubyとしては新しいオブジェクトが生成・返されてしまう)。
それでは、GtkWindowをどのような実装にすればよいか。
以下のようなソースを見てみる。
class GtkWindow
def initialize
@store = {}
end
def child
val = gtk_widget_get_child(self) #実際のCのコードを呼び出すイメージ
@store["child"] << val
val
end
def set_child(val)
gtk_widget_set_child(self, val)
@store["child"] << val
self
end
end
つまり、GTKのAPIに渡した情報であったとしても、PureRubyWindowと同様な動きが期待されるメソッドでは、GtkWindowのインスタンス変数としてcreate_windowで渡されるchildを保存しておくということだ(childそれ自身に覚えさせておくのではない、というのがポイント)。
こうしておくことで、少なくともGtkWindowの生存期間とchildの生存期間を合わせることができる....はず。
■ 実はRuby-GNOME2では、これの局所的な問題解決用にG_RELATIVE2というマクロが定義されている(正直忘れてた....(^^;))。
で、それを使って上記のGtkWindowをもうちょっとCっぽく書いてみる。
#ただ、今見直すと、第3引数が不要と思われるのでここでは省略。
class GtkWindow
def child
val = gtk_widget_get_child(self) #実際のCのコードを呼び出すイメージ
G_RELATIVE2(self, val, "child"); //"child"はHashのkey。
//この"child"は文字列ではない方が良いかも。
val
end
def set_child(val)
gtk_widget_set_child(self, val)
G_REMOVE_RELATIVE(self, "child");
G_RELATIVE2(self, val, "child");
self
end
end
要はこれをRuby-GNOME2全体に適用すればよいのだけど...。もうちょっと整理しないとしないとだなぁ。ということで以下にTODOをあげておく。
- まず、どういった基準で上記を適用するか、全てのCのWrapperメソッドで適用していいのか。
- 上記のマクロは実装も含めもう少し整理した方が良いな。
GOBJ2RVAL3(key, self, gobj)/RVAL2GOBJ3(key, self, val)みたいなのを作っても良いかもしれない。 - G_RELATIVE/G_RELATIVE2の実装は見直したほうがいいなぁ...。
- 上記はsetter/getterの組み合わせで成立する。addとかinsertとか、同じ変数(らしきもの)に値がどんどん追加されるようなものはもう少し検討が必要そう。
- プロパティやシグナルなど、自動的に生成されるRubyオブジェクトにも同様な仕掛けを入れ込むが必要あるかも。
■ さてさて。どうしたもんかなぁ。まぁ、問題自体が今になって顕在化するようなものだから今さら焦ってもしょうがないよね(?)。ゆっくりやるとするか...。ってかホントにこれで大丈夫なのか不安だ(苦笑)。
■ しかし、このTomaszって人すごいなぁ。こんな短期間によくここまで考察できたなぁ。いっそのこと彼に書き直してもらえないかな(^^;)。
