Ruby win32ole 拡張モジュール覚え書き(6)

Win32OLEの製作過程の目次へ

Ruby 1.3.4 対応

Ruby 1.3.4 がリリースされ,1.3 系も安定期に入って, 1.4 のリリースも近づきつつあるようですので, win32ole も 1.3.4 でコンパイルすることにしました.

Ruby 1.2.X 用の win32ole はそのままではコンパイルが通りません. 拡張モジュールで利用している Ruby の関数名が違っているからです. 名前を変更しないといけないので, 1.3.4 の sample ディレクトリにある rename.rb を使って, 一括変換をしました.

でもって,コンパイル.1件だけエラーがでました.

rb_obj_call_init(1.2の obj_call_initに対応) の引数の数が変わったようです. Ruby のソースの他の部分を調べてみると,

rb_obj_call_init(obj, 0, 0);

というのと

rb_obj_call_init(obj, argc, argv);

というのがあったので,rb_obj_call_init(obj, 0, 0); を試してみました. 名前から想像すると,これは,Ruby のスクリプトレベルの initialize を呼んでいるんじゃないかと思います. 本当のところはどうするのが良いのかと迷っています. ・・・別に呼ばなくても問題ないような気もするし.

とにもかくにも,この程度の作業で,win32ole が 1.3.4 でも動きました.

1999.07.17 追記:
rb_obj_call_init は,やはりスクリプトレベルの initialize を呼んでいるそうです.


Cygwin32 対応

Cygwin32 用に win32ole 拡張モジュールを一部修正している という話題が,Nifty でありました. その差分を送っていただいて,本体に取り込ませていただきました. (わたなべさんに感謝)
私は,取り込んだだけで確認はしてません.


CD-ROM

9月頃出ると噂の Ruby 本の CD-ROM に収録してもらうには Ruby 1.3 系に対応してないといけないそうです.

今対応しているのをそのままリリースしても良いのですが, 付け加えようと思っていた機能があるので, やる気を出すことにしました. 他人様からきっかけを与えてもらわんと何もできない私.


for そして 0.0.4リリース

OLE のコレクションオブジェクトに対しては for が使えると 便利かなと思い,試しに動くかなと思って

for i in Sheets
  puts i.name
end

とやってみたのですが,each メソッドがないと叱られました.

やっぱり自分が作った覚えのない機能は動かないか. うちには小人さんがいないらしい. でも,each メソッド追加すればよさそう. IEnumVARIANT を使って while 文でまわして, rb_yield を呼べば each メソッドを 作れそうだし.まあ,簡単そうだな.
・・・などと考えてから3ヶ月近く経ってしまいました. (3ヶ月経っても私の家には小人さんが現れませんでした.)

簡単そうだという予想はあたりました. わりとすんなりと実装できました. 後は for を使ったサンプルを1つ追加して, ドキュメントに手を加えれば, 1.3 対応版 win32ole 0.0.4 のリリースです.


const_load

win32ole を使った人から「定数を使えるようにして」という要望がありました. これはやらなきゃいけないリストのトップにあった項目なのですが ずっとほったらかしにしてました. 仕様をどうすれば使いやすいか悩んでいたこともありました. 使った人からの要望には, 「ユーザーが指定したクラスの定数として定義できると嬉しい」 みたいなことが書いてあったので,ちょっとそれも考慮することにしました. 要望がきたので, win32ole.c と Perl の OLE.xs とか Const.pm とかを眺めながら, ごそごそと3時間ほど OLE と戯れてみました.

Perl では定数の定義の仕方がいろいろあるんですが そのうち最も簡単に実装できる定義の仕方だけ win32ole で実装することにしました. (作者の都合が最優先でユーザーの都合は後回しの win32ole.)

簡単なスクリプトを書いて試してみたところ遅い. プログラムが死んだかと思うほどです. 良く考えてみたら,Perl の Const も一度も使ったことが無かったので 速度比較のために試してみることに. ・・・なんや.一緒やん. 単にパソコンが遅かっただけか.

(註:私は Perl のスクリプトはろくに書けません. ですから速度を追求する書き方をしていないので同じ速度なのかも知れません. もっとも Ruby のスクリプトも速度を追求する書き方はしてませんが.)

定数が使えるようになったので version 0.0.5 をリリース.

そうそう.Excel VBA や Perl では xlTop で定数の参照ができますが, Ruby では,定数は大文字で始めるという制限のため XlTop となります.


日付の変換

Ruby のメーリングリストで

Win32OLE でとってきた日時を Ruby の Time オブジェクトに変換できませんか?

という要望が流れました. version 0.0.5 までは日時は,システムの表示形式にあわせた形で 文字列にして返していました. で,なんでそうしたかというと何か理由があったような気もしますが思い出せません. まあ,Perl の OLE もそうなっているので,それにあわせたというのも理由の一つなんですが.

確かにいわれてみれば,表示形式にあわせて文字列で返すと加工しづらいと思ったので, Time オブジェクトへ変換することにしました.

OLE の日付は,内部では,double 型の浮動小数点数の値として管理されています. 整数部が 1889/12/30 からの経過日数を表現しています. 整数部がマイナスのときは,1899/12/30 より前の日付を示しています. 小数部は,時刻を1日を1.0に換算したときの割合を示しています. たとえば,

1.0 ---> 1899/12/31
2.0 ---> 1900/1/1
2.25 ---> 1900/1/1 6:00:00
-1.0 ---> 1899/12/29

という感じです.これを Time オブジェクトに変換しなくてはなりません. 変換アルゴリズムとして使えるものがないかと調べてみたところ, Ruby 標準ライブラリの date.rb date2.rb がありました. 結局 date2.rb のアルゴリズムを利用することにしました. ついでに Ruby の Time オブジェクトは,OLE の日付型に変換するようにしました.


Time オブジェクトは却下

変換できるように実装してテストしていたんですが問題が1つ発生しました. 1970/1/1 以前の日付は,例外が発生して Time オブジェクトに変換できません.

  1. Time オブジェクトに変換して,例外が発生したときは文字列にする.
  2. 年,月,日,時,分,秒をそれぞれ配列で返す.
  3. 浮動小数点数をそのまま Ruby の Float にして返す.
  4. 常に文字列で返す.

といろいろ考えたのですが,

  1. 場合によって文字列になったり Time オブジェクトになったりすると 日付を取り出した後で,Win32OLE を使うユーザーが 型チェックをする必要があるので却下

  2. これは,

    ex = WIN32OLE.new('Excel.Application')
      ...
    sh = ex.workbooks(1).worksheets(1)
    t = sh.Range("A1").value  #[1]
    sh.Range("B1").value = t  #[2]
    

    というスクリプトを考えたときに[1]のところで配列が返ってくると [2]で セル B1 には配列を入れようとするので時刻が入りません. したがって却下します.

  3. これは,Ruby 側で扱いにくいと思うので却下.

ということで,常に文字列で返すことにしました. ただし今までのフォーマットだと加工しにくいでしょうから常に

"YYYY/MM/DD hh:mm:ss"

のフォーマットの文字列で返すことにしました. これだったら Time オブジェクトに変換するのも

Time.mktime(*t.split(/[\/ :]/))

でできます.

上の2の反論であげたスクリプトもちゃんと期待通りに動きます. これは "1999/11/26 23:24:18" という 文字列を代入しても([2]の処理) Excel 側で時刻だと勝手に解釈して日付型に変換してくれるからなんです. (逆に「勝手に解釈」してくれるせいで痛い目を見ることもあるんですが.)


Win32OLEの製作過程の目次へ