師星です。

Gtk::CTreeでGCのmark中にときたまsegmentation faultを起こす件と、
Gtk::CListにset_row_dataしたオブジェクトが該当rowの削除後もCList本体の解
放までGCされない件を修正するパッチを作りました。

まずGtk::CTreeについてですが、node_set_row_data()したものについて
CTreeNodeからgc_markしようとするとsegmentation faultを完全に避けるのは
難しいように思います(GTK+またはGLibのバグもあるかも知れませんが)。
そこで、gc_markする責任をCTreeに持たせることを考えました。

良く考えるとこれはCListでも同様で、GtkCListへのポインタを
Data_Wrap_Struct()してCListのインスタンス変数として持たせ、
そのmark関数に、CListが持つrowを調べてset_row_dataされたオブジェクトを
マークする関数を与えるようにしました。
結局、CTreeの方ではこの関数をそのまま流用して同じようなインスタンス変数
を持たせるだけで済みました。

五十嵐さんのスナップショットではCTreeについては違うアプローチがなされて
いますが、これで問題が解決すると思うので、取り込んでいただけると幸いです。

以下、gtk-0.23へのパッチと簡単なテストプログラムです。
テストプログラムではnodeまたはrowをremove、GCを起動、オブジェクトの数を
確認することが出来ます。

diff -urN -x *.orig -x *.bak -x *~ gtk-0.23.orig/src/rbgtkclist.c gtk-0.23/src/rbgtkclist.c --- gtk-0.23.orig/src/rbgtkclist.c Sat Jan 8 20:52:28 2000 +++ gtk-0.23/src/rbgtkclist.c Fri Sep 29 14:48:08 2000 @@ -13,9 +13,29 @@ #include "global.h" +static ID id_marker; + /* * CList */ + +void +clist_marker_mark(clist) + GtkCList *clist; +{ + if (clist) { + GList *list; + for (list = clist->row_list; list; list=list->next) { + if (list->data) { + GtkCListRow *row = GTK_CLIST_ROW(list); + if (row->data) { + rb_gc_mark_maybe(row->data); + } + } + } + } +} + static VALUE clist_initialize(self, titles) VALUE self, titles; @@ -38,6 +58,8 @@ widget = gtk_clist_new(NUM2INT(titles)); } set_widget(self, widget); + rb_ivar_set(self, id_marker, Data_Wrap_Struct(rb_cData, clist_marker_mark, + 0, widget)); return Qnil; } @@ -438,7 +460,6 @@ clist_set_row_data(self, row, data) VALUE self, row, data; { - add_relative(self, data); gtk_clist_set_row_data(GTK_CLIST(get_widget(self)), NUM2INT(row), (gpointer)data); return self; @@ -649,6 +670,8 @@ { gCList = rb_define_class_under(mGtk, "CList", gContainer); + id_marker = rb_intern("marker"); + /* Signals */ rb_define_const(gCList, "SIGNAL_SELECT_ROW", rb_str_new2("select_row")); rb_define_const(gCList, "SIGNAL_UNSELECT_ROW", rb_str_new2("unselect_row")); diff -urN -x *.orig -x *.bak -x *~ gtk-0.23.orig/src/rbgtkctree.c gtk-0.23/src/rbgtkctree.c --- gtk-0.23.orig/src/rbgtkctree.c Sat Jan 8 20:52:29 2000 +++ gtk-0.23/src/rbgtkctree.c Fri Sep 29 14:54:11 2000 @@ -14,23 +14,16 @@ #include "global.h" static ID id_nodelist; +static ID id_marker; -static void -ctree_node_mark(node) - GtkCTreeNode *node; -{ - if (node) - if (GTK_CTREE_ROW(node)) - if (GTK_CTREE_ROW(node)->row.data) - rb_gc_mark_maybe(GTK_CTREE_ROW(node)->row.data); -} +extern void clist_marker_mark(GtkCList *clist); VALUE make_ctree_node(node) GtkCTreeNode* node; { if (!node) return Qnil; - return Data_Wrap_Struct(gCTreeNode, ctree_node_mark, 0, node); + return Data_Wrap_Struct(gCTreeNode, 0, 0, node); } static GtkCTreeNode* @@ -87,6 +80,8 @@ nodelist = rb_ary_new(); rb_ivar_set(self, id_nodelist, nodelist); + rb_ivar_set(self, id_marker, Data_Wrap_Struct(rb_cData, clist_marker_mark, + 0, widget)); return Qnil; } @@ -1425,6 +1420,7 @@ gCTreeNode = rb_define_class_under(mGtk, "CTreeNode", rb_cData); id_nodelist = rb_intern("nodelist"); + id_marker = rb_intern("marker"); /* constants */
#!/usr/local/bin/ruby require 'gtk' ID = Object.new ID.instance_eval { @no = 0 } def ID.next @no += 1 @no end class Foo def initialize @s = ID.next.to_s end def to_s @s end end ######################################################################## # GUI ######################################################################## def build_gui window = Gtk::Window.new(Gtk::WINDOW_TOPLEVEL) window.signal_connect('destroy') { Gtk::main_quit } vbox = Gtk::VBox.new false, 0 window.add vbox vbox.show scrwin = Gtk::ScrolledWindow.new scrwin.show scrwin.set_usize 200, 200 vbox.pack_start scrwin, true, true, 0 ctree = Gtk::CTree.new 1,0 ctree.show scrwin.add ctree buttonbox = Gtk::HButtonBox.new buttonbox.show vbox.add buttonbox add_child = Gtk::Button.new 'Add Child' add_child.show buttonbox.add add_child add_sibling = Gtk::Button.new 'Add Sibling' add_sibling.show buttonbox.add add_sibling remove = Gtk::Button.new 'Remove' remove.show buttonbox.add remove print_foo = Gtk::Button.new 'Print' print_foo.show buttonbox.add print_foo buttonbox2 = Gtk::HButtonBox.new buttonbox2.show vbox.add buttonbox2 count_foo = Gtk::Button.new 'Count Foos' count_foo.show buttonbox2.add count_foo gc = Gtk::Button.new 'GC' gc.show buttonbox2.add gc selected_node = nil ctree.signal_connect('tree-unselect-row') { buttonbox.set_sensitive false selected_node = nil } ctree.signal_connect('tree-select-row') { |tree,selected_node,column| buttonbox.set_sensitive true } ctree.insert_node nil, nil, ['root'], 0, nil, nil, nil, nil, false, true buttonbox.set_sensitive false $nodelist = [] add_child.signal_connect('clicked') { new_foo = Foo.new node = ctree.insert_node selected_node, nil, [new_foo.to_s], 0, nil, nil, nil, nil, false, true ctree.node_set_row_data node, new_foo ctree.expand selected_node $nodelist << node } add_sibling.signal_connect('clicked') { new_foo = Foo.new node = ctree.insert_node selected_node.parent, selected_node, [new_foo.to_s], 0, nil, nil, nil, nil, false, true ctree.node_set_row_data node, new_foo $nodelist << node } remove.signal_connect('clicked') { ctree.remove_node selected_node } print_foo.signal_connect('clicked') { STDERR.print ctree.node_get_row_data(selected_node), "\n" } count_foo.signal_connect('clicked') { n = 0 ObjectSpace.each_object(Foo) { |o| n += 1 } STDERR.print "# of Foo: #{n}\n" n = 0 ObjectSpace.each_object(Gtk::CTreeNode) { |o| n += 1 } STDERR.print "# of CTreeNode: #{n}\n" } gc.signal_connect('clicked') { GC.start } window.show end ######################################################################## # main ######################################################################## if __FILE__==$0 build_gui Gtk::main end
#!/usr/local/bin/ruby require 'gtk' ID = Object.new ID.instance_eval { @no = 0 } def ID.next @no += 1 @no end class Foo def initialize @s = ID.next.to_s end def to_s @s end end ######################################################################## # GUI ######################################################################## def build_gui window = Gtk::Window.new(Gtk::WINDOW_TOPLEVEL) window.signal_connect('destroy') { Gtk::main_quit } vbox = Gtk::VBox.new false, 0 window.add vbox vbox.show scrwin = Gtk::ScrolledWindow.new scrwin.show scrwin.set_usize 200, 200 vbox.pack_start scrwin, true, true, 0 clist = Gtk::CList.new 1 clist.show scrwin.add clist buttonbox = Gtk::HButtonBox.new buttonbox.show vbox.add buttonbox add_child = Gtk::Button.new 'Add Child' add_child.show buttonbox.add add_child add_sibling = Gtk::Button.new 'Add Sibling' add_sibling.show buttonbox.add add_sibling remove = Gtk::Button.new 'Remove' remove.show buttonbox.add remove print_foo = Gtk::Button.new 'Print' print_foo.show buttonbox.add print_foo buttonbox2 = Gtk::HButtonBox.new buttonbox2.show vbox.add buttonbox2 count_foo = Gtk::Button.new 'Count Foos' count_foo.show buttonbox2.add count_foo gc = Gtk::Button.new 'GC' gc.show buttonbox2.add gc selected_row = nil clist.signal_connect('unselect-row') { buttonbox.set_sensitive false selected_row = nil } clist.signal_connect('select-row') { |list,selected_row,column,event| buttonbox.set_sensitive true } clist.insert 0, ['root'] buttonbox.set_sensitive false add_child.signal_connect('clicked') { new_foo = Foo.new row = clist.insert selected_row+1, [new_foo.to_s] clist.set_row_data row, new_foo } =begin add_sibling.signal_connect('clicked') { new_foo = Foo.new node = ctree.insert_node selected_node.parent, selected_node, [new_foo.to_s], 0, nil, nil, nil, nil, false, true ctree.node_set_row_data node, new_foo } =end remove.signal_connect('clicked') { clist.remove_row selected_row } =begin print_foo.signal_connect('clicked') { STDERR.print ctree.node_get_row_data(selected_node), "\n" } =end count_foo.signal_connect('clicked') { n = 0 ObjectSpace.each_object(Foo) { |o| n += 1 } STDERR.print "# of Foo: #{n}\n" } gc.signal_connect('clicked') { GC.start } window.show end ######################################################################## # main ######################################################################## if __FILE__==$0 build_gui Gtk::main end
ついでに、Gtk::refとGtk::unrefも取り込んでいただけないでしょうか^^; Pixmapを使い回したりというときに必要なので……。
diff -urN -x *.orig -x *.bak -x *~ gtk-0.23.orig/src/rbgtk.c gtk-0.23/src/rbgtk.c --- gtk-0.23.orig/src/rbgtk.c Fri Feb 11 19:20:42 2000 +++ gtk-0.23/src/rbgtk.c Fri Sep 29 14:30:01 2000 @@ -921,6 +921,24 @@ } static VALUE +gobj_ref(self) + VALUE self; +{ + GtkObject *object = get_gobject(self); + gtk_object_ref(object); + return self; +} + +static VALUE +gobj_unref(self) + VALUE self; +{ + GtkObject *object = get_gobject(self); + gtk_object_unref(object); + return self; +} + +static VALUE gobj_unset_flags(self, flags) VALUE self, flags; { @@ -1017,6 +1035,8 @@ * instance methods */ rb_define_method(gObject, "initialize", gobj_initialize, -1); + rb_define_method(gObject, "ref", gobj_ref, 0); + rb_define_method(gObject, "unref", gobj_unref, 0); rb_define_method(gObject, "flags", gobj_get_flags, 0); rb_define_method(gObject, "flags=", gobj_set_flags, 1); rb_define_method(gObject, "flags!=", gobj_unset_flags, 1); diff -urN -x *.orig -x *.bak -x *~ gtk-0.23.orig/src/rbgtkclist.c gtk-0.23/src/rbgtkclist.c
-- MOROHOSHI Akihiko <moro / remus.dti.ne.jp>