[Ruby] WIN32OLE_EVENT#unadvise
WIN32OLE_EVENTオブジェクトを途中でGCできないのは、
次のようなスクリプトを考慮しているから
ie = WIN32OLE.new('InternetExplorer.Application')
ev = WIN32OLE_EVENT.new(ie, 'DWebBrowserEvents')
ev.on_event{|*args| handler(*args)}
# ここでGC発生
ie.visible = true
ie.navigate(url) # handlerに通知したい
# ここで二度目のGC発生
while true
WIN32OLE_EVENT.message_loop
sleep 0.1
end
ev.on_event でイベントハンドラを定義しても、
その直後にGCが発生してevがGCされてしまうと
イベントを拾うことができない。
そこで、WIN32OLEオブジェクトがGCされない間、
つまり ieがGCされない間、evがGCされなければいいじゃん
という意見を頂戴した。
一瞬、いい案に思えたんだけど、上の例の二度目の
GCの後でも、ieはGCされても、InternetExplorerのプロセスは残るし
マウスで、Internet Explorerを操作できる。
その間にevがGCされると、イベントを拾うことができなくなる。
(現状では、evはGCされないので、イベントを拾い続ける。)
結局のところ、単純に ev がGCされるてもいいときは、
通知を受ける必要がなくなったときで、
それは、スクリプトを書いている人が判断すりゃいいじゃん
という投げやりな結論になってしまった。
ということで、もう通知は必要ないよというためのメソッド
WIN32OLE_EVENT#unadviseを trunkに追加してみた。
本当にGCされるかどうかは、まだ検証していないのだけど、
unadviseするとそれ以降は、通知されなくなるのは確認済み。
こういうのを用意すると、逆のWIN32OLE_EVENT#adviseも
欲しくなるんじゃないかと思うのだけど、
そっちは手元でテスト中なのでコミットはまだ。
名前は、IConnectionPoint の Unadviseから取ったんだけど、
ピンとこない人の方が多いかな。
| 固定リンク
« [英語] マシュマロ盗難の謎は解決されるもののの、Alfred Hitchcock というのは、あのHitchcockなのかどうなのか最後までよくわからず、結局、湖の中にいたSomethingも何なのかはっきり読み取れなかった分、どこか消化不良のまま終わってしまったJigsaw Jones シリーズ | トップページ | [英語] いかにも動機が子供らしいと思ってしまったJigsaw Jonesシリーズ »
この記事へのコメントは終了しました。
コメント
まるでユーザではないのですが。。。
ie.instance_variable_set("@event", ev)
な状態(「ieがGCされない間、evがGCされなければいいじゃん」)のあとに
ie = nil
GC.start
というような状態になったら「通知を受ける必要がなくなったと」「スクリプトを書いている人が判断」したと動くのがRubyっぽいかなぁと思いました。
明示的に切り離すメソッドも必要だとは思いますが、それはデフォルトじゃない方がいいかなぁと思いました。
Internet Explorerが死ぬまでイベントを拾い続けるようにするのなら、Internet Explorerが死ぬまでevをGCしないようにするというのはどうでしょうか。
できるのかどうかわかりませんが、内部にevをGCから保護するようなテーブルみたいなのを持って、Internet Explorerが死んだイベントがきたらそのテーブルからevを削除する、みたいなことはできないのかなぁと思いました。
(Ruby/GTK2はこんな風にしたはず。)
こうすれば、スクリプトが終了しなくてもevが必要なくなったらGCされる気がします。
投稿: kou | 2007年9月 5日 (水) 13時02分
1つ目の案は現行のバージョンと動作が異なるので、躊躇してます。逆に意識しなくても、ieがGCされるときにはGCされちゃうので、その瞬間からイベントが飛んでこなくなると、あれ?なんで?と戸惑う人が出てくるんじゃないかと。
2つ目の案は、実は実際にやってみました。
1つ問題があって、IEの終了のタイミングとRubyの終了のタイミングがほぼ同時の場合、IEからの通知を受けてテーブルからevを削除しようとするのと、Rubyスクリプト終了時のテーブルまるごとGCがぶつかることがあるみたいでコアダンプすることがあり、実現できていないんです。
それさえうまくクリアできれば、2つ目の案を採用したいところです。
投稿: suke | 2007年9月 5日 (水) 20時18分
なるほど。
やはり、もうすでに試していたのですね。。。
で、懲りずにアイディアだけですが、
テーブルにオブジェクトをぶちこむんじゃなくて、markとIEが死んだかを表すflagを使うのはどうでしょうか?
RubyのGC中にIEが死んで通知がきても、(VALUEじゃない)flagをいじるだけになるのでRubyは死なない気がします。
(GCが走ってmarkが呼ばれるときはflagを見てマークするかどうかを変えてやる)
flagの置き場所ですがoleeventdataにひとつフィールドを追加しちゃうだけでよさそうな気はします。
投稿: kou | 2007年9月 6日 (木) 13時48分
すいません。ちょっとよくわからなかったのですが、
Data_Make_Struct(k, oleeventdata, ev_mark, pev);
みたいにして
ev_mark(oleeventdata *pev) {
if (pev->flag) {
rb_gc_mark する。
}
}
みたいなことでしょうか?
これって、oleeventdata自身に自分のVALUEを持つということになるんでしょうか?それともなんか勘違いしてますか?
投稿: suke | 2007年9月 6日 (木) 20時42分
はい、そういうことを言いたかったです。
oleeventdataは自分のVALUEを持たなくてもよいと思っていました。
IEからの、俺死んだよ、通知がどうやってくるのかがわからないのですが、oleeventdataだけの情報で通知を受けることができると思っていました。
で、それができるのなら、その通知を受けたときにflagを1にしてやればいいかなぁと思っていました。
# よくわからないくせにごちゃごちゃ混乱させてすみません。
投稿: kou | 2007年9月 8日 (土) 09時47分
rb_gc_markとかの活用は考えたことが無かったので参考になりました。ありがとうございます。
自分でも、まだ問題が整理ができていないように感じているので、もう少し考えてみます。
投稿: suke | 2007年9月 8日 (土) 16時44分
あぁ。。。
ごめんなさい、ごめんなさい、ごめんなさい。。。
私、本当にアホなことを書いていました。
その通りです。VALUEがないとマークできないです。
# 自分自身もマークできると勘違いしていた。。。
言いたかったのはIEが死んだときの通知にfreeしないで、freeするのはGCのときだけにすればどうかしら、ということだったんです。。。
本当にすみませんでした。。。
マークで頑張るならWIN32OLE_EVENTのインスタンスを管理するグローバルなオブジェクトを一つ用意しなきゃいけないと思います。
って、これもまた余計なことになるかも。。。
投稿: kou | 2007年9月 8日 (土) 19時31分
そうですよねえ。やっぱりVALUEを持つのを用意しないと駄目ですよね。すっきりしました。よかったです。
いろいろ考えたんですが、根本的に、IEからの通知とGCのタイミングは同期していないように思えるので、いずれにせよ途中でevをGCするのはまずい場合がありそうな気がしてきてます。
投稿: suke | 2007年9月 8日 (土) 23時39分