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

Win32OLE 製作過程の目次へ

調査

Perl の OLE 拡張モジュールのソース, Nifty-Serveのフォーラム,Windows プログラミング関連の書籍を調べて, OLE の IDispatchインターフェースと, IDispatchインターフェースがもつInvoke関数 が鍵だということがわかってきました.

IDispatch 変数を内部にもつ Ruby のオブジェクトを作って そのオブジェクトに Invoke を起動するメソッドを定義しておけば 何とかなりそうだと見当をつけました.


名前の決定

拡張モジュールの名前は,「win32ole 拡張モジュール」に決めました. 別に「OLE拡張モジュール」でも良いかなとも考えましたが, Win32環境だけをターゲットとするという意味でwin32を つけておくことにしました. Win32API 拡張モジュールに合せておいた方が良いかとも考えたためです.

クラス名も拡張モジュールの名前に合せて"WIN32OLE"と 決めました. さほど深くも考えずに, WIN32OLE は Object のサブクラスにしておくことにしました.


構造体の定義

IDispatch 変数(Cの変数)を Ruby のオブジェクトとして扱うために, IDispatch をメンバーにもつCの構造体を定義します.
struct oledata {
    IDispatch *pDispatch;
};
この構造体を Ruby のDataオブジェクトと結び付けてやれば, Ruby のオブジェクトのできあがりです. (結び付けるためのCのマクロが Ruby では用意されています. Data_Make_StructData_Wrap_Struct です.)

実は,IDispatch へのポインタ変数1つだけであれば, わざわざ構造体にする必要はありません. そのまま IDispatch へのポインタ変数と Ruby の Dataオブジェクトを 結び付ければ良いのです. ですが,今後の拡張のことを考えて,構造体にしておくことにしました. (実際,参考にした Perlのソースコードでは,IDispatch 以外の変数も扱っています.)


IDispatchの取得と処理の流れ

IDispatch インターフェースを取得して何かして終了する流れは, 大雑把に以下のようになります.
  1. OLE を使用するために OleInitialize 関数を実行して初期化する.
  2. CLSIDFromProgID関数を実行して 指定されたOLEオートメーションサーバー名の文字列からCLSIDを取得する.
  3. CoCreateInstance 関数を使って CLSIDからIDispatchインターフェースを取得する.
  4. 何かやりたいことをやる.
  5. OleUninitialize を実行して OLE の後始末する.
この流れをCのコードで書けばこんな感じです.
(エラーチェック等を省略してますのでそのまま使わないように.)
CLSID clsid;
IDispatch *pDispatch;

/* OLE の初期化 */
OleInitialize(NULL);
/* OLEオートメーションサーバー名(ProgID)から CLSID を取得 */
CLSIDFromProgID(L"Excel.Application", &clsid);
/* CLSID から IDispatch インターフェースを取得する */
CoCreateInstance(&clsid, NULL, 
                 CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
                 &IID_IDispatch, (void**)&pDispatch);
/* 何かやりたいことをする */
  ・・・
/* OLE の後始末する */
OleUninitialize();

new メソッド

まず考えた,win32oleの仕様の(かなりC言語よりの)イメージはこんな感じでした.
require "win32ole"
ole = WIN32OLE.new
#OLE の初期化
ole.initialize
#Excel.Application の IDispatchインターフェースの取得
disp = ole.get_dispatch("Excel.Application")
#何かする
  ・・・
#OLE の後始末
ole.uninitialize

初期化と後始末を意識しなくてはならないのは, Rubyらしくなくて格好悪いです. こんな感じの方がスマートです.

require "win32ole"
#初期化は意識しない
disp = WIN32OLE.new("Excel.Application")
#何かする
 ・・・
#後始末は意識しない
そこで としました.
(初期化と後始末の構造は,Ruby の socket 拡張モジュールを参考にしています.)

Win32OLE 製作過程の目次へ