書いた人: cuzic
cuzic です。
とうとう、今回の記事でこの Win32OLE の連載も最終回を迎えます。 感慨深いです。 皆さんの応援や素晴らしいアドバイザーの方々の助言、そして みなさんのご愛読の賜物であると思っております。
今回のこの記事はこれまでの連載の中でもかなり毛色が変わった 記事になります。 というのは、実は Ruby のことではなくて、Ruby 以外の他の言語に おいて、OLE/COM/ActiveX の技術がどう利用できるのかという 内容について説明するものだからです。
海外に行くと日本の文化や人間同士のコミュニケーションについて 意外な点に気づかされた経験なんかはありませんか? 他の会社に行った昔の友達と話したとき、自分の会社がどんな会社で あるかに改めて気づくことができたりしますよね。
今回は、あえて Ruby 以外の言語での COM サポートについて 紹介することで、Ruby での COM がどういう実装なのかということや COM という技術がどういう技術なのかということについて 理解を深めてもらうということを狙っています。
今回の記事では次の各種言語での COM 技術について紹介しようと 思っています。
それでは、順に説明していきましょう。
なお、今回の記事は各種言語において現状で筆者が調査できた範囲での COM 技術について紹介することが目的です。 筆者の調査の限界や言語への理解などの不足によって、十分に正確でない 記述があるかもしれないことを最初にお詫びしておきます。
各言語での COM 実装で、COM の機能の中でどれだけ使えるかについて この記事では比較していきます。 例えば、イベントの実装方法、COM サーバを作成できるか、 コレクションの要素をイテレートする方法、タイプライブラリで 定義された定数の利用法などについて説明していこうと思います。
さらに、Internet Explorer を使ってイベントなどを扱うような簡単な スクリプトを提示して、比較していきます。
この記事を読んでいる人の多くは今回扱う言語をすべて知っている わけではないでしょうが、それぞれの言語の文法などに ついての解説は行いません。 言語の文法について知りたい方は各自でそれぞれの言語の 解説を行っているページなどを参考にしてください。 また、言語の比較などについて知りたい方は、 Ruby versus Smalltalk versus Objective-C versus C++ versus Java versus Python versus CLOS versus Perl5 を参照してください。
VBScript は、Microsoft 社が提供しているスクリプト言語で、 Visual Basic と非常によく似た言語体系になっています。
VBScript の特徴は、Windows 標準で使える WSH (Windows Script Host) でサポートされているスクリプト言語 である点でしょう。そのため、どこでも動くということが必要と される場合で COM の技術を使いたい場合に重宝します。
説明はこれくらいにして、VBScript での COM を使うスクリプトの 例を紹介しましょう。
Sub ie_DownloadComplete()
WScript.Echo "Download Complete"
End Sub
Dim ie
Set ie = WScript.CreateObject("InternetExplorer.Application","ie_")
ie.Visible = True
ie.GoHome()
While ie.ReadyState <> 4
WScript.Sleep(1000)
Wend
ie.Navigate("http://www.ruby-lang.org/")
While ie.ReadyState <> 4
WScript.Sleep(1000)
Wend
Dim element
Dim count
count = 0
For Each element In ie.Document.all
count = count + 1
Next
WScript.Echo("complete" & vbCrLf & count & "elements found")
この例はとても簡単な例です。 VBScript で Internet Explorer を起動して、 ホームページ(ブラウザで表示される最初のページ)を 表示させて、その表示が完了すればその次に Ruby のページを 表示させています。 このスクリプトではイベントハンドラを使っています。 DownloadComplete というイベントが発生する度に 「Download Complete」というメッセージをポップアップさせています。
これからもこのスクリプトと同様の動作をするスクリプトを 用いて、各種言語での COM 実装について説明していきます。 この動きについてはよく理解しておきましょう。
VBScript で COM を利用するときは、WScript.CreateObject メソッド を呼び出します。他に CreateObject 関数を使う方法もあります。 この 2 つは、よく似ています。しかしながら、 CreateObject が VBScript で用意されている関数であるのに対して、 WScript.CreateObject は WSH のメソッドです。
この2つは COM オブジェクトを返す点は同じですが、とる引数が違います。
1番目の引数は同じように ProgID をとりますが 2 番目の引数は、 違います。 WScript.CreateObject メソッドは、2 番目の引数として イベント 処理を行う関数の名前のプレフィックスをとります。 CreateOjbect 関数ではオブジェクトを生成するコンピュータの名前 を指定します。
このスクリプト中では、WScript.CreateObject メソッドを使うことで、 イベント呼び出しを行うプレフィクスを指定しています。 これで、イベントが発生したときは、 「ie_イベント名」という名前のメソッドを探して、あれば それを実行します。
VBScript では、COM のタイプライブラリで定義された定数を 利用することはできません。そのため、オブジェクトブラウザなどで あらかじめ調べて、その値を直接スクリプト中に書く必要があります。
例えば、次のところでは、READYSTATE_COMPLETE という定数を 使おうとしています。4 は、READYSTATE_COMPLETE です。
タイプライブラリで定義された定数をどうしても使いたい場合は、 拡張子を WSF として XML 形式のファイルを作成する方法が あります。
このようにすれば、先ほどの 4 という数字が直書きされていた 場合に、 READYSTATE_COMPLETE という定数で記述できます。
上と同じ処理を実行するスクリプトの場合は次のようになります。 (注、スクリプトを含んだファイルが添付できないようだったので、直書きです)
XML 形式でどの ProgID を使うのかについて明示的に指定することで、 定数を記述できるようになります。
なお、上記の XML は 「<」「>」「&」というような記述がそのまま 使われている点が気になる方もいらっしゃると思います。 今回のスクリプトはこのままで動作します。
それは、WSF の形式として、一番最初に XML 宣言を記述しなければ ルーズに解釈するからです。 詳しくはWindows Script Components <?XML ?>を参照してください。
本来記事として公開するものとしては、XML の形式に厳格な方が好ましいの かもしれませんが、今回は読みやすさを重視してルーズな書き方をしました。 ご了承ください。
JScript というのは、Netscape の JavaScript に互換性がある言語として Microsoft が実装した言語です。 JScript の特徴は、JScript Feature Informationで簡潔にまとまっています。
一般に HTML に埋め込んで Web ページを動的にするために用いられる JScript ですが、COM クライアントを作成するための言語としても 使うことができます。
VBScript とともに Windows Script Host で標準でサポートされ ている言語です。
文法上の違いはもちろんありますが、JScript を COM で利用する ときの特徴は VBScript の場合ととてもよく似ています。 似ている例として、イベントハンドラの定義の仕方や、 タイプライブラリで定義された定数を使用することについてよく似ています。
VBScript のところと同じ処理を行うコードの例は次のとおりです。
var ie = WScript.CreateObject("InternetExplorer.Application","ie_");
ie.Visible = 1;
ie.GoHome();
while (ie.ReadyState != 4) {
WScript.Sleep(1000);
}
ie.Document.onclick = function (){
WScript.Echo("click!!");
};
ie.Navigate('http://www.ruby-lang.org/');
while (ie.ReadyState != 4) {
WScript.Sleep(1000);
}
var element,count=0;
for(element in ie.Document.all){
count += 1
}
WScript.Echo("complete\n" + count + " elements found\n");
function ie_DownloadComplete() {
WScript.Echo("Download Complete");
}
文法上の違いを除いてはほとんど VBScript と同じような流れで 記述可能であることが分かると思います。
個人的に JScript が面白いと思う点としては次のような コードを記述可能な点です。
JScript では関数はオブジェクトの一種ですので、 変数に代入することもできます。 そして、上記のように無名関数を生成して、Document オブジェクトの イベントハンドラとして使用するために、イベントである onclick というプロパティに代入できます。
Python は動的型付けのオブジェクト指向スクリプト言語の1つです。 Ruby と比較されることが多いため、ご存知の方も多いと思います。 Python の公式サイトは Python Programming Language です。
Python での COM 技術は、とても幅広くサポートされています。 特筆すべき点として、COM サーバが作成可能であることです。 Python での COM サーバの作成については洋書で良ければ参考書籍もあり、 Google で調べれば様々な情報が手に入ります。
また、COM MakePy ユーティリティによって、アーリーバインディングが とても簡単に行えます。 さらに、ctypes という ライブラリを利用することによって、型変換が非常に手軽に行えます。
では、VBScript で行ったのと同様の例を Python で説明しましょう。
import win32com.client
from win32com.server import util
import pythoncom
class WebBrowserEvent:
def OnDownloadComplete(self,*args,**kwds):
print "Download Complete"
def OnQuit(self,*args,**kwds):
exit
def com_collection_iter(collection):
return (collection.Item(index)
for index in range(1, collection.Count+1))
#ie = win32com.client.Dispatch("InternetExplorer.Application")
ie = win32com.client.DispatchWithEvents("InternetExplorer.Application",WebBrowserEvent)
ie.Visible = True
ie.GoHome()
while ie.ReadyState != win32com.client.constants.READYSTATE_COMPLETE:
pass
ie.Navigate('http://www.ruby-lang.org/')
while ie.ReadyState != win32com.client.constants.READYSTATE_COMPLETE:
pass
count = 0
for element in ie.Document.all:
count += 1
print "complete\n %d elements found\n" % (count)
Python で、COM オブジェクトを作成するには、単に win32com.client.Dispatch というメソッドを使います。 今回は、イベントハンドラを定義して、そのイベントハンドラを使う形で COM オブジェクトを作成しようとします。イベントハンドラを指定して COM オブジェクトを作成するときは、win32com.client.DispatchWithEvents を 使います。
Python では、タイプライブラリで定義された定数を利用できます。 Ruby では、指定したモジュール内に定義されますが、Python では win32com.client.constants という名前空間で定義されます。
COM の定数を利用するには、あらかじめ MakePy ユーティリティを使って、 利用可能な状態にしておく必要があります。 MakePy ユーティリティは、PythonWin というツールからメニューで 選択できます。
Python でのイベントハンドリングは、上記のスクリプトのように あらかじめイベントハンドラのためのクラスを定義することによって行います。
この節は ActiveState からダウンロードできる ActivePython の バージョン 2.4.1 で検証しました。
Perl をご存知でない方はおそらくいないでしょう。 Perl についての情報はPerl.com から得られます。 Ruby の中には Perl を参考にしてもたらされた機能がいくつもあります。
Perl では、COM オブジェクトを作成するには、Win32::OLE という モジュールを使います。Win32OLE ライブラリの作者の助田さんによると、 このライブラリ名は Perl の Win32::OLE という名前を参考にして つけられたらしいです。
Perl で同様に InternetExplorer を制御するスクリプトを紹介すると 次のようになります。
use Win32::OLE qw(EVENTS in);
use Win32::OLE::Const ('Microsoft Internet Controls');
$| = 1;
my $ie = Win32::OLE->new('InternetExplorer.Application');
Win32::OLE->WithEvents($ie,"WebBrowserEvents","DWebBrowserEvents2");
$ie->{Visible} = 1;
$ie->GoHome();
Win32::OLE->MessageLoop();
while($ie->ReadyState() != READYSTATE_COMPLETE){
}
$ie->Navigate('http://www.ruby-lang.org/');
Win32::OLE->MessageLoop();
while($ie->ReadyState() != READYSTATE_COMPLETE){
}
my $count = 0;
foreach my $element (in $ie->Document->all){
$count++;
}
print "complete\n ${count} elements found\n";
package WebBrowserEvents;
sub DownloadComplete {
my ($obj,@args) = @_;
print "Download Complete\n";
}
sub NavigateComplete2 {
my ($obj,@args) = @_;
Win32::OLE->QuitMessageLoop();
}
という行で InternetExplorer のオブジェクトを生成しています。
で、イベントを WebBrowserEvents というパッケージに定義された イベントハンドラで処理させることができます。
あと、Perl の Win32::OLE->MessageLoop() は、Ruby の WIN32OLE_EVENT.message_loop とは動作が違います。 Perl では、Win32::OLE->QuitMessageLoop() が呼び出されることで、 メッセージループを抜けます。 Ruby では、一瞬メッセージループを通ったのちにまたすぐに処理が Ruby 側に戻ります。
あと、Perl では定数をロードするために、最初に
と宣言しています。 これを書くことによって、スクリプト中に READYSTATE_COMPLETE という定数を使うことができます。
この節は、ActiveState からダウンロードできる ActivePerl の バージョン 5.8.7.813 で検証しました。
Tcl というのは、Tcl/Tk に採用されているプログラミング言語として 有名です。 Tcl Developer Siteで様々な情報が得られます。 Tcl には tcom というライブラリがあり、 tcom を使うことで Tcl を使って、COM のテクノロジを利用できます。 Tcl で、COM のサーバも作成可能です。
今までと同様の処理を行うスクリプトを紹介しましょう。
package require tcom
proc sink {method args} {
if {$method eq "DownloadComplete"} then {
puts "event $method $args"
}
}
set ie [::tcom::ref createobject "InternetExplorer.Application"]
::tcom::bind $ie sink
$ie Visible 1
$ie GoHome
while {[$ie ReadyState] != 4} {
}
$ie Navigate "http://www.ruby-lang.org/"
while {[$ie ReadyState] != 4} {
}
set count 0
set elements [[$ie Document] all]
::tcom::foreach element $elements {
incr count
}
puts "complete\n $count elements found"
Tcl で、COM オブジェクトを作成するには、
と実行します。
そして、イベントハンドラを使うには、次のように実行します。
Tcl の場合は、様々な COM のイベントをひとつのプロシージャで処理することに なります。
あと Tcl の場合は、foreach のループを Tcl の言語が用意しているものではなく、 tcom のライブラリで提供しているものを使う必要があるという点が 特筆すべきかもしれません。
Tcl で COM の定数が使えるのかどうかについては調査不足で調べきれませんでした。
この節は、ActiveTcl バージョン 8.4.11.0 で検証しました。
Java を使えるプログラマの数はとても多いことでしょう。 Java という文化は 100% Pure であり Microsoft の技術を使わないことに こだわる部分があるので、意外に思われる方がいるかもしれませんが、 Java でも COM のテクノロジを利用できるようにするプロジェクトがあります。 もちろん、J++ や J# のような Microsoft が 関与している Java の処理系で COM を使えますし、 それ以外にも jacob や jcom という Java で COM を利用可能に するライブラリがあります。
ここでは jacob を使った例を紹介します。 今回のプログラムで行う処理は今までと全く同じわけではありませんが、ご了承ください。
import com.jacob.com.*;
import com.jacob.activeX.*;
public class InternetExplorer
{
public static void main(String[] args)
{
ActiveXComponent iecom = new ActiveXComponent("InternetExplorer.Application");
Object ie = iecom.getObject();
Dispatch.put(ie, "Visible", new Variant(true));
IEEvents ieE = new IEEvents();
DispatchEvents de = new DispatchEvents((Dispatch) ie,ieE,"InternetExplorer.Application");
Dispatch.callSub(ie,"GoHome");
try {
while(Dispatch.get(ie,"Busy").toBoolean() == new Boolean(true)){
Thread.sleep(4000);
}
Dispatch.call(ie,"Navigate",new Variant("http://www.ruby-lang.org/"));
while(Dispatch.get(ie,"Busy").toBoolean() == new Boolean(true)){
Thread.sleep(4000);
}
Dispatch document = Dispatch.get(ie,"Document").toDispatch();
Dispatch all = Dispatch.get(document,"all").toDispatch();
int num_element = Dispatch.get(all,"length").toInt();
System.out.println("complete\n" + num_element + "elements found");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
iecom.invoke("Quit",new Variant[]{});
}
}
}
class IEEvents
{
public void DownloadComplete(Variant[] args) {
System.out.println("DownloadComplete");
}
}
このプログラムで、Java から COM を呼び出すことができます。 Java の場合は、今まで紹介した言語と違って、静的型付け言語である点が色濃く 反映されたコードになっています。
つまり、メソッドを動的に呼び出すことができないため、Dispatch.get の ようなメソッドを用いて、プロパティの値を取得したり、その取得した値を 明示的に型変換したりする必要があります。
COM オブジェクトの作成は次のようにします。
イベントを処理するクラスを次のように定義します。
上記のクラス定義を見ると分かるように IEEvents クラスは、 なんらかのインタフェースを implememts するというような記述を 書く必要がありません。 これは、Java らしいスタイルではなく少し意外な感じがします。
タイプライブラリに定義された定数の取得は、私が調査した限りでは できないようでした。
この節は、Sun の JSK 1.5.0_14 、 jacob Version 1.9 で検証しました。
プログラミング言語 Lua は、 ブラジルで開発されたプログラミング言語で、非常に 軽量で組み込み用途に向いている言語です。 Lua は手続き記述型の簡素な言語ですが、オブジェクト指向的な仕組みを提供し、 動的な型付けやガベージコレクションなど便利な機能があります。 最近個人的に注目している言語です。 Luabind や Luanet、 など、興味深いプロジェクトが精力的に活動しています。 リファレンスマニュアルの日本語訳 もあります。
そして、Lua の一種として COM の技術を利用可能にした LuaCOM もあります。
LuaCOM は、COM サーバーも作成可能でかつ、COM クライアントも作成可能な Lua のライブラリ兼実行形式です。
では、他の言語と同じように Lua で IE を操作するデモを紹介しましょう。
require("luacom")
local ie = luacom.CreateObject("InternetExplorer.Application")
local events_handler = {}
function events_handler:DownloadComplete()
print "Download Complete"
end
local cookie = luacom.Connect(ie,events_handler)
ie.Visible = 1
ie:gohome()
while ie.ReadyState ~= 4 do
end
ie:Navigate("http://www.ruby-lang.org/")
while ie.ReadyState ~= 4 do
end
count = 0
for index,element in luacom.pairs(ie.Document.all) do
count = count + 1
end
print(string.format("complete \n%d elements found",count))
次の行で Lua での COM オブジェクトの作成を行っています。
Lua でのイベントの取り扱いについては、イベントの実装を行ってるテーブル を COM オブジェクトに接続することによって行います。 次の 4 行がこの処理に該当します。 なお、テーブルというのは Lua の基本的データ構造です。 テーブルは Ruby でのハッシュや配列、はたまたクラスを表現するとき にも使える非常に強力なデータ構造です。
LuaCOM には LoadConstants という定数をロードするためと 思われる API があるのですが、いかんせん使い方がよく 分からなかったので使っていません。
Lua でコレクションの各要素に対してイテレーションを行うには、 luacom.pairs という関数呼び出しを使います。
なお、
の cookie という返り値は使っていませんが、これを利用して、 LuaCOM ではコネクションを開放できます。
この節は、LuaCOM Version 1.5 で検証しました。
最後に Ruby の場合に上記の例がどうなるのかを紹介しましょう。
require 'win32ole'
module InternetExplorer
end
ie = WIN32OLE.new('InternetExplorer.Application')
WIN32OLE.const_load(ie,InternetExplorer)
event = WIN32OLE_EVENT.new(ie,"DWebBrowserEvents2")
event.on_event("DownloadComplete") do
puts "Download Complete."
end
ie.Visible = true
ie.GoHome
while ie.ReadyState != InternetExplorer::READYSTATE_COMPLETE
WIN32OLE_EVENT.message_loop
end
ie.Navigate 'http://www.ruby-lang.org/'
while ie.ReadyState != InternetExplorer::READYSTATE_COMPLETE
WIN32OLE_EVENT.message_loop
end
count = 0
ie.Document.all.each do
count += 1
end
puts "complete\n#{count} elements found"
Ruby での Win32OLE には次のような特徴があります。
このスクリプトを見て思う点は、Ruby は非常に柔軟で動的な言語であるということです。 Ruby の定数が動的にロードできるという点や、ブロック付メソッド呼び出しが 文法として用意されている点が十分に活用されて、Win32OLE のライブラリが 作られているということを感じます。
ここで紹介した以外の言語でも、COM は使えます。
たとえば、Microsoft が提供している数多くの言語で COM は 利用可能です。 .NET Framework では、タイプライブラリをインポートすることで COM の呼び出しが可能になることは有名だと思います。 その機能を利用することによって、.NET Framework 上で動く さまざまな言語で COM の技術を使用できます。 もちろん C# でも使えます。 たとえば毛色の変わったところで .NET Framework で動く OCaml ベースの 関数型言語である F# のような言語で COM を使えます。 関数型言語で自由にプログラミングできるとうきうきします。
また、Groovy という最近注目されている Java 上でのスクリプト言語でもこの記事でも取り上げた jacob と 組み合わせて COM を操ることができる Scriptom という Groovy モジュールがあります。
ただ、Scriptom は筆者が調査した限りではイベントハンドリングなど いくつかの点でまだまだ心許ない部分が多いようでした。
変わりどころでは、xyzzy という Emacs と似た操作性を持つエディタの Lisp 環境でも COM を使うことができます。
xyzzy で COM を扱う簡単なデモを紹介すると次のような感じです。
エディタからブラウザや Excel はたまた iTunes などの COM 対応の アプリケーションをぐいぐい動かせると思うと楽しいですね。
なお、xyzzy の COM/OLE Automation の機能が実装される際には Ruby の Win32OLE を参考にされたようです。そう聞くと少し 親近感がわいてきます。
ただ、xyzzy というエディタは実際使おうとするとドキュメントがどこにあるのか よく分からないところがあるので、注意が必要です。
同様にエディタから COM 呼び出しができる例として、EmEditor が あります。興味があれば調べてみると面白いかもしれません。
今回紹介しませんでしたが、最近 Ruby コミュニティで注目されている Haskell という言語でも COM/ActiveX を利用できる HaskellScript があります。
最後にもう少し Ruby についての話題に触れておきます。 他の言語から Ruby のメソッドを呼び出す方法として、 ActiveScriptRuby を 使う方法があります。
次のデモのコードが分かりやすいかもしれません。
Dim sc
Set sc = CreateObject("ScriptControl")
sc.Language = "GlobalRubyScript"
sc.AddCode("def add x,y;x + y;end")
WScript.Echo(sc.Run("add",2,3))
Ruby で定義した add というメソッドを Visual Basic から利用しています。 ScriptControl という COM オブジェクトと ActiveScriptRuby を使用することで、 COM を使用できる任意の言語に Ruby を埋め込んで使うことができます。 ActiveScript として使える言語としては私の調べた限り、JScript、VBScript、Ruby、Python、 Perl、Tcl、PHP があります。 つまり、COM を使用できる任意の言語から、JScript/VBScript/Ruby/Python/Perl/PHP などを埋め込んで使うことができます。 次の EmEditorのマクロを様々なActiveScriptで書いてみる で行われているようなことが可能です。
Excel VBA などを書いているときにどうしても Visual Basic で 実現できないことを Ruby でやりたいときなどに便利かと思います。
COM は様々な言語で利用可能なようにインタフェースを定めており、 そのおかげで COM のインタフェースを用意するだけで、 開発者は自分の好きな言語でその COM サーバを制御する スクリプトを書くことができます。
また、COM サーバも様々な言語で書くことができ、 Python や Tcl、Lua のような言語で作った COM サーバを 他の様々な言語で呼び出して使うことができます。 他にも Windows スクリプト コンポーネント を 利用すれば、Perl や Ruby といった ActiveScript に対応した言語ならば 実は COM サーバを作成可能です。 Perl の場合は、ActivePerl Documentation の Windows Script Componentsの説明が参考になるでしょうし、 Ruby の場合は Ruby を256倍使う本 邪道編 が参考になるでしょう。
Microsoft は、COM で実現した様々な言語を混在して使える環境をさらに発展させた ものとして、現在 .NET と総称して呼ばれる一連の環境を提供しています。
Microsoft のロードマップにしたがって言うと、この OLE/COM/ActiveX の テクノロジは .NET ベースの言語にこれから置き換えられていくことと思います。 .NET は最初スクリプト言語への対応が弱かったのですが、だんだんと面白いものが提案 されてきました。 IronPython や Ruby/.NET Bridge など 興味深いです。 とくに Microsoft Research で研究されている言語は面白いです。 プログラム言語が好きなら、 Programming Principles and Tools で 何が行われているかは、追いかけて損はないと思います。
また、日本の未踏ソフトウェア創造事業で取り上げられたものとして Ruby.NET コンパイラの開発 もあります。
今回でこれまで連載を続けてきました Win32OLE 活用法は終わります。 最終回は Ruby の雑誌なのに Ruby のスクリプトがあまり出てこない不思議な 回でした。楽しんでもらえましたでしょうか?
つまらない日常の仕事は、ちょっとしたプログラミングをすることでもっと楽に 行えるようにできると思います。 そのためにこの記事が一助になれば幸いです。
cuzic は、プログラミング言語について調べることが好きです。 新しい未知の言語の特徴について調査したり、 OLE/COM/ActiveX や .NET 、luabind、 Boost.Python といった言語を横断するような技術に強い興味があります。