[戻る]

GtkTreeModelFilter使用時の注意点(あるいはバグの反省)

モデルデータ変更時に発生するイベント内でGtkTreeModelFilterを扱う場合の注意点です。
環境依存なのかもしれませんが、ちょっと手順が違うとコア吐いて落ちます。
FreeBSD 7.1-RELEASEのportsコレクションから入れたバージョン2.14.7と2.16.1で確認しました。

トラブル - GtkTreeModelFilterを使用して行を削除するとプログラムが落ちる?

GtkTreeViewのモデルにGtkTreeModelFilterを使用し、かつGtkTreeSelectionchangedイベントハンドラでモデルデータを参照するようにしているとき、選択中のモデルデータを削除するとコアを吐いて落ちててしまいました。
コンソールには以下のような内容が出力されています。
(gtktreemodelfilter_trouble:1167): Gtk-CRITICAL **: gtk_list_store_get_value: assertion `VALID_ITER (iter, list_store)' failed

(gtktreemodelfilter_trouble:1167): GLib-GObject-WARNING **: gtype.c:3940: type id `0' is invalid

(gtktreemodelfilter_trouble:1167): GLib-GObject-WARNING **: can't peek value table for type `<invalid>' which is not currently referenced
Segmentation fault (core dumped)
連続した行を削除するとコアを吐いて落ちます。
一行ずつ削除しているときや削除する行が連続していないときは落ちないか、落ちる確率が低いようです。

最初は行を削除しているだけなのに、何処からgtk_list_store_get_value()が呼ばれて、何故iterが無効になっているかさっぱりわかりませんでした。
よくよく調べてみると、以下のようなことをしていました。
  1. GtkTreeViewのモデルにGtkTreeModelFilterを使用。
  2. gtk_tree_list_store_remove()で選択中の行を削除する。
  3. 選択中の行が削除されたのでGtkTreeSelectionchangedイベントが発行される。
  4. changedイベントのハンドラ内で、現在選択中の行のGtkTreePathのリストを取得する。
  5. GtkTreePathからgtk_tree_model_get_iter()GtkTreeIterを取得する。
  6. 取得したGtkTreeIterGtkTreeModelFilterのものなのでgtk_tree_model_filter_convert_iter_to_child_iter()で大元のGtkTreeModelGtkTreeIterを取得する。
  7. gtk_tree_model_get()GtkTreeModelのデータを取得し、選択データの情報をステータスバーに表示。
上記最後の7.のgtk_tree_model_get()から呼ばれるgtk_list_store_get_value()内でエラーが発生して落ちているようです。

落ちる箇所が判明したところで、今度は原因を考えてみます。
gtk_tree_model_get()は受け取ったGtkTreeIterをそのままgtk_list_store_get_value()に渡しているので、changedイベントハンドラ内の処理に原因があるということになります。
コンソールにはiterが不正だというメッセージが出力されているので、どうやら上記の5.と6.でGtkTreeModelFilterGtkTreePathからGtkTreeModelGtkTreeIterを取得する方法に問題がありそうです。
[TOP]

解決策 - GtkTreeIterの取得方法を変更

問題は自分で書いたGtkTreeSelectionchangedイベントハンドラ内の処理、GtkTreeIterの取得方法にありそうだという所まで判ったので、別の方法を試してみます。

別の方法といっても出来る事は一つしかありません。
落ちていた時はGtkTreeModelFilterGtkTreePathからGtkTreeIterを取得し、その後GtkTreeModelGtkTreeIterに変換していましたが、これを変更して先にGtkTreePathgtk_tree_model_filter_convert_path_to_child_path()GtkTreeModelのものに変換し、それからgtk_tree_model_get_iter()GtkTreeIterを取得するようにします。
これでどうやら落ちることは無くなったようです。

ということでコアダンプするコードと対策コードを纏めました。gtktreemodelfilter_trouble.c
文字コードはUTF-8で保存してください。
GtkTreeSelectionchangedイベントで選択中の行の値の合計値を計算して表示。削除ボタンを押すと選択中の行を削除します。
GtkTreeModelFilterはただ使用しているだけです。常に全ての行を表示します(フィルタリングしていません)。
普通にコンパイルするとコアを吐いて落ちます。
マクロFIX_CONVERT_TO_CHILDを定義すると落ちなくなります。

詳しくは判らないのですが現象だけを見ると、GtkTreeModelFilterを使用しているとき、フィルタリングしているGtkTreeModelから行を削除すると、GtkTreeModelFilterへの反映が遅れるか何かして、GtkTreeSelectionchangedイベントでGtkTreeModelFilterから取得したGtkTreeIterが不正となるか、またはgtk_tree_model_filter_convert_iter_to_child_iter()での変換が正しく行なわれないということのようです。

最初にgtk_tree_model_get_iter()GtkTreeIterを取得しておく場合と比べて、最初にgtk_tree_model_filter_convert_path_to_child_path()で変換する場合は、gtk_tree_path_free()を呼び出す手間が一つ増えるので、それを嫌って自然と落るコードを書いてしまったのが敗因でした。

これはFAQなのかもしれません。とてもFAQっぽいです。
やってはイケナイ事として既にドキュメントになっていそうです。(>_<)
[TOP]
[戻る]