ある日,私宛に直接メールが届きました.
接続可能オブジェクトに接続できる機能をつけてください.
というリクエストが届きました.
「まかせなさい」と返事を出す前に まず『接続可能オブジェクト』について調べてみることにしました. そうです. このとき私は生まれて初めて, 『接続可能オブジェクト』なる言葉に遭遇したのでした.
接続可能オブジェクトというのは, OLE クライアントに対してイベントを通知できる OLE サーバーのことのようです.
例えば,Ruby のスクリプトで
Excel(=OLEサーバー) を起動して, イベント待ち状態にして,セルの値が代わったら何か処理する.
ということができるようになるということかもと期待し, なかなか面白そうだと思いました. 例によって,Perl の OLE のソースを調べてみたところ その機能をサポートしていると思われる部分を見つけました.
まだどうやって実装するか,できるかどうかもわからなかったので 「まかせなさい」という返事は出せませんでしたが, 「やってみる気になっている」という感じの返事を出しておくことにしました.
接続可能オブジェクトですが,Perl の OLE では,WithEvents という クラスメソッドを用意して扱うようにしているようです.
$Excel = Win32::OLE->new('Excel.Application'); sub Event { ... } Win32::OLE->WithEvents($Excel, &Event, 'AppEvents');てな感じです. Ruby でも同じようにしようかと思ったんですがどうもしっくり来ません. うまく説明できないんですがなんかしっくりこない.気に入らないんですね. で結局イベントを扱うクラスがあった方がなんとなく Ruby らしいかなと思い WIN32OLE_EVENT クラスを用意することにしました. イベントのコールバックルーチンはイテレータで指定できるような感じが Ruby らしいでしょう.こんな感じでしょうか?
excel = WIN32OLE.new('Excel.Application') def event(event, *args) ... end excel_event = WIN32OLE_EVENT.new(excel, 'AppEvents') excel_event.on_event{|*args| event(*args)}さらに個々のイベントに対するコールバックルーチンも指定できた方が便利かも知れません. こんな感じです.
def sh_sel_change(*args) end excel_event.on_event('SheetSellectionChange'){|*args| sh_sel_change(*args)}あとはこれをどうやって実現すれば良いかです.
WIN32OLE_EVENT.new の実装とOLE サーバーと会話する処理(イベントを待つようにする処理)は, Perl の WithEvents の実装を参考にすれば何とかなりそうな感じ.
イテレータでコールバックを指定する部分は...いいのがありました.Ruby-Gtk 拡張モジュールです. ふむふむ.なるほど. rb_f_lambda() 関数を使ってイテレータブロックを手続きオブジェクトにできるみたい. この手続きオブジェクトを VALUE に保存しといて,イベント通知が来たときに手続きオブジェクトを 呼び出せば良いかな. 手続きオブジェクトの呼び出しは,rb_apply を使って call メソッドを呼び出せばできそう.
static VALUE callback; ... f_on_event(...) { ... callback = rb_f_lambda(); ... } ... STDMETHODIMP EVENTSINK_Invoke(...) { ... rb_apply(callback, rb_intern("call"), args); ... }てな感じですね.大体のところは見当がついたのであとは実装するだけです.
WIN32OLE_EVENT クラスの実装もそれなりにできたみたいなので バージョン 0.0.6 として公開.
ところが,リクエストしてくださった方や他の方から NT だと終了しないとの報告が来ました. 最後まで実行するらしいんですが,その後,プロンプトに戻ってこないとのことです. Windows 95 だとちゃんと(かどうかはわからんけど)終了して,プロンプトに戻って来ます. NT をもっている人に協力してもらいながら,調査することに.
結局調査した結果,Release し忘れというのが原因だったようです. OLE の世界では
- インターフェースを取得する.
- 取得したインターフェースを開放されないように AddRef を実行する.
- 取得したインターフェースを使ってやりたいことをする.
- 取得したインターフェースが用済みになったら Release を実行して, 開放しても良いことをサーバーに通知する.
という手順を踏むのですが,厄介なことにインターフェースを取得したときに 暗黙に AddRef を呼び出されている場合もあるんですね. その場合でも,Release だけは必ず呼び出さないといけないんです. Release の呼び忘れがいくつかあったんですね.
NT で終了しなかったのは,Release しなかったためにインターフェースは いつまでたっても用済みにならず,最後まで処理が進んだ後で終了しなかった訳です. メモリリークみたいなものですね. Release を呼ぶようにしたら正常に終了するようになったそうです. (私自信は NT を持ってないので,終了しない状態というのは見たことがないのですが NT を持っている方に確認していただきました. そもそも NT で Ruby が動いているのを見たこともありません.)
ここまでたどりつくのに結構疲れました.当分 OLE はいいやという気分です. 私につきあってバグフィックスに協力してくださったみなさんには本当に感謝してます. とにもかくにもバグフィックス版のリリースへ.
と思っていたら全く違う方法を使った NT 対応のためのパッチが Ruby の ML で流れました. 私の開発環境で試してみたらこのパッチでも大丈夫そうだったのですが, DCOM がインストールされていない Windows 95 だと駄目だということがわかりました.
実は,私が初めて Win32OLE を実際に使うようになったのがこの頃 (最初のリリースから1年経過して初めて使うようになった.)なのですが その環境というのが DCOM のない Windows 95 でした. このパッチを採用してしまうと私自身が一番困ることになります.
他人のことはともかく自分が困るのは嫌なので, パッチを流してくださった方には申し訳なかったのですが, 採用しませんでした. (もっとも,Windows 95 がこの世からなくなったときや, 私の対応方法で何か不都合がおきたときに備えて,パッチは保存してあります.) 今度こそ本当にバグフィックス版 0.0.7 のリリースです.
世の中って甘くないです.(世の中のせいにしてはいけません) Win32OLE#each メソッドに同様の NT でだけ発生するバグがまだ残っていました. 協力してもらってようやっとバグフィックス. Release 意外にも,VariantClear を忘れたことがバグの原因.
私がボーっと遊んでいる間に Cygwin 環境でコンパイルできたと 報告をいただきました. パッチのうち Win32OLE 本体側で取り込める部分は 取り込んでおくことに. ソースを公開しておいて良かった.
Excel 2000 を使っている場合に
class Excel end excel = WIN32OLE.new('Excel.Application') WIN32OLE.const_load(excel, Excel)を実行すると途中で Ruby のエラーが出るそうです. Excel 2000 には,'_' で始まる定数があるみたいです. 一方 Ruby の定数は大文字で始めないといけないという 制限があります. 小文字で始まる定数は,大文字に変換しているのですが '_' は大文字に変換できないので, 定数を定義するときにエラーが発生するのです.
対策としては
- 大文字に変換できない定数は無視する.
- Perl みたいにハッシュにする.
- 大文字に変換できない場合は,先頭に 'CONST' をくっつけて 無理矢理大文字にする.
など考えたのですが,結局
- 大文字に変換できる場合は従来通り
- 変換できない場合は,CONSTANTS['_XXX'] のようにハッシュで アクセスできるようにする.
ということに試験的にしてみました. 上のスクリプトの例では '_' で始まる定数は,Excel::CONSTANTS['_XXX'] で得ることができます.
Excel 2000 をもってない私は,テストしてませんので, 動いたというレポートやこうした方が良いという意見は歓迎します. CQN02273 at nifty.ne.jp にメールをいただけると嬉しいです.