Ruby による Win32OLE プログラミング - FileSystemObject を使う
Ruby による Win32OLE プログラミング -
FileSystemObject を使う
はじめに
ここでは,Windows Scripting Host (WSH) に付属している FileSystemObject を使ってみましょう.FileSystemObject は,Win32 ファイルシステムにアクセスするために使われる OLE オブジェクトです.
なぜ FileSystemObject を取り上げたのかというと,Ruby のファイルシステムの機能が Win32 環境では弱いためです.例えば,ファイルセパレータが バックスラッシュ '\' ではなく '/' になっており,この指定を変えることはできないようです.また,ファイル名に「表」などヤバそうな文字が含まれている場合,File#expand_path メソッドが正しく動作しません (少なくとも ruby-1.4.4 mswin32 版では).
Excel プログラミングで,Excel ファイルをオープンするとき,ファイルセパレータは バックスラッシュでないとダメですし,オープンする Excel ファイル名はフルパスでなければならないため,File#expand_path がちゃんと使えないのは痛いです.そこで File#expand_path の代わりに FileSystemObject を使うことにしましょう.
VBE の参照設定
VBE (Visual Basic Editor) を起動し,FileSystemObject のライブラリを参照設定します.ライブラリ名は,`Microsoft Scripting Runtime' です.
参照設定したあと,オブジェクトブラウザをオープンします.オブジェクトブラウザのライブラリを Scripting に設定し, FileSystemObject のクラスを表示させます(下図).
このように,このブラウザを使えば FileSystemObject がもつメソッドとプロパティがわかります.
FileSystemObject の生成
FileSystemObject を Ruby の Win32OLE モジュールを使って生成しましょう.Ruby のプログラムは次のようになります.
require 'win32ole' instance = WIN32OLE.new('Scripting.FileSystemObject')
`Scripting.FileSystemObject' が FileSystemObject のプログラム識別子です.この識別子を WIN32OLE クラスの new メソッドに渡すと対応する OLE オブジェクトが生成されます.これは, Win32OLE プログラムの基本となる部分です.クラス名はすべて大文字の `WIN32OLE' です.
プログラム識別子の調べ方
プログラム識別子ですが,どうやって調べるかは COM について不勉強のためよくわかりません(もしご存知の方がいらっしゃればぜひ教えてください).
わかっていることは,レジストリの HKEY_CLASSES_ROOT 内に少なくとも同じ識別子が存在する,ということぐらいなんですよね(たぶん).だから,何も情報がない場合は,レジストリエディタを開いてそれらしいものがないかトライ&エラーで調べています.非常に効率が悪いですが,他にやり方を知りません.
もっとましな方法は,VBScript や WSH の本を調べることです.VBScript の場合は,上の Ruby スクリプトは次のようになります.
Dim instance Set instance = WScript.CreateObject("Scripting.FileSystemObject")
このように,CreateObject メソッドに渡す引数がプログラム識別子になっていますので,VBScript で書かれたサンプルコードから生成したい OLE オブジェクトの識別子はすぐにわかります.
Singleton パターン
さて,このようにして作った FileSystemObject を他からアクセスするにはどうするのがよいでしょうか?
この オブジェクトは,フォルダの生成,ファイルの移動,パスの取得など Win32 ファイルシステムそのものを表しています.使いたい場所で個別に FileSystemObject を生成するのも無駄ですし,かといって使いたい場所まで引数で渡していくのも面倒です.
そこで FileSystemObject に対して Singleton パターン を使います.
module FileSystemObject @instance = nil def FileSystemObject.instance unless @instance then @instance = WIN32OLE.new('Scripting.FileSystemObject') end return @instance end end
こうすれば,使いたい場所で FileSystemObject.instance を呼べばいいですし,WIN32OLE モジュールを使っているということすら意識する必要がありません.
なお,Singleton パターンといっても厳密には GoF のものとちょっと違いますが,本質は Singleton です.
RubyUnit によるテスト
さて,WIN32OLE オブジェクトが生成されたかテストをしてみましょう.
テストを行うには,RubyUnit が最適です.ここでは RubyUnit の使い方を詳しく説明しませんが,後に出てくる test なんとかメソッドの中にあるコードがそのクラスの使い方や仕様だと理解していただければ十分です.(興味のある方は助田さんのサイトから最新版をゲットして実際に使ってみてください.以下のメソッドは,RubyUnit が提供する TestCase クラスから継承したテストクラスのメソッドです.そのままでは実行できません)
以下は FileSystemObject クラスの instance メソッドのテストコードです.
def test_instance instance = FileSystemObject.instance assert_instance_of(instance, WIN32OLE) end
このテストコードは,FileSystemObject.instance で得られたオブジェクトが WIN32OLE クラスのインスタンスである,ということを表しています.
本当はこのテストだけでは不十分です.というのも,そのオブジェクトが WIN32OLE インスタンスだからといって FileSystemObject のメソッド `GetFolder' を受け取ることができるとは限らないからです.得られたオブジェクトが 本当に FileSystemObject のOLE オブジェクトかどうかを簡単に判定する方法は今のところよくわかりません.試しに
def test_has_get_folder_method instance = FileSystemObject.instance assert_respond_to(instance, 'GetFolder') end
としましたが,やはりダメでした(assert_respond_to は インスタンスがメッセージを受け取るかどうかをテストします).これは,GetFolder が WIN32OLE クラス本来のメソッドではないせいでしょうか(詳しいことはわかりません).
WIN32OLE インスタンスが本当に GetFolder メソッドを持っているかどうかは実際に実行してみるしかないようです.しかし,単にメソッドを持っているかどうかのテストなら,副作用なしのリードオンリーのテストを行いたいところです.もしリードオンリーでなくてもかまわないのなら,例えば
def assert_has_ole_method(instance, method_name, *arg) assert_no_exception do instance.invoke(method_name, *arg) end end
を作って
def test_has_get_folder_method instance = FileSystemObject.instance assert_has_ole_method(instance, 'GetFolder', '.') end
でできなくはありませんが.まあ,しょうがないのでメソッドを持っているかどうかのテストではなく,実際にメソッドを使ったテストコードを書いて,例外が発生しないかどうか見るほうが自然でしょう.
FileSystemObject の利用
さて,実際に FileSystemObject を使ってみましょう.まずカレントディレクトリのパスを求めてみます.先ほど登場した GetFolder メソッドは,相対パス名を引数としてを渡すとそのパスに対応する Folder オブジェクトを返します.また,Folder オブジェクトの path プロパティは読み取り専用プロパティで,絶対パス名の文字列を返します.したがってカレントディレクトリとその親ディレクトリを表示するのプログラムは以下のようになります:
fileSystem = FileSystemObject.instance puts fileSystem.getFolder('.').path puts fileSystem.getFolder('..').path
次に,このセクションの目的である File#expand_path の代わりに FileSystemObject を利用することを考えます.FileSystemObject の GetAbsolutePathName メソッドは,File#expand_path に相当するメソッドです.このメソッドが実際にどのような仕様になっているのかを RubyUnit による テストコードを書いて確かめます.
最初にテストクラスの準備を行う setup メソッドで FileSystemObject をインスタンス変数 @fileSystem に取得しておきます.
def setup @fileSystem = FileSystemObject.instance end
その後 @fileSystem を使って GetAbsolutePathName のテストコードを書くと,以下のようになります.
def test_getAbsolutePathName current_dir = @fileSystem.getFolder('.').path parent_dir = @fileSystem.getFolder('..').path actual = @fileSystem.getAbsolutePathName('filename') assert_equal(current_dir + '\\' + 'filename', actual) actual = @fileSystem.getAbsolutePathName('表示') assert_equal(current_dir + '\\' + '表示', actual) actual = @fileSystem.getAbsolutePathName('申請') assert_equal(current_dir + '\\' + '申請', actual) actual = @fileSystem.getAbsolutePathName('.') assert_equal(current_dir, actual) actual = @fileSystem.getAbsolutePathName('..') assert_equal(parent_dir, actual) end
上のテストコードで,assert_equal メソッドは,第1引数が期待値,第2引数が実測値を表しており,両者が等しくなければならない,ということを示しています.また,冒頭で述べたヤバそうな文字が含まれている場合のテスト,引数として相対パスを指定したときのテストも含めています.
スクリプトファイル
以上のスクリプトを file-win32.rb として用意しました.また,このスクリプトの RubyUnit を使ったテストコードも file-win32-test.rb として用意しています.実行する場合は, ruby に -Ks オプションをつけてください(このオプションは,シフトJIS としてスクリプトを扱うことを意味しています).