HTAからWScriptへアクセスしてみた ― 2007年06月05日 20時15分12秒
とりあえずのテスト
6/1のエントリで紹介した、「Windows Script Programming」さんのWindowsアプリからWScript.exeのWScriptオブジェクトを利用するをとりあえず試してみた。
元々頭の中にあったのが「WScriptオブジェクトをHTAに渡せば、HTAからグローバルメンバにアクセスできるぜ。へへへ」てなことだったのだが、なんかイマイチうまくいかなかった。
よくよく考えてみると、単体のJSファイルでも
WScript == thisはfalseになるので無理だったのだが、それがわかるまで結構ハマった。
なので、IEを経由させるのはWScriptではなくthisにするようにしてみた。
他にも型のチェックとかで元のコードを単純に移行しても動かなかったのと、もらったWScriptからWshShellを作成してPopup叩いてもイマイチ面白くないので、HTAからWScript.EchoとWScript.StdIn.ReadLineを試してみるようにした。
で、作ってみたコード
まずjs側。名前を「wsh.js」とする。
function echo(s) {
WScript.Echo( s );
}
function readln(msg) {
if(msg) WScript.StdOut.Write(msg);
return WScript.StdIn.ReadLine();
}
// 終了待ち合わせフラグ
var quit = false;
var sh = new ActiveXObject("WScript.Shell");
var shell = new ActiveXObject("Shell.Application"), ie;
for(var col = new Enumerator( shell.Windows() ); ! col.atEnd(); col.moveNext()) {
if( col.item().hWnd == WScript.Arguments.Item(0) ) {
ie = col.item();
break;
}
}
ie.PutProperty( "WScript", this );
// HTAからquitにtrueがセットされるまで待ち合わせ
while( ! quit ) {
WScript.Sleep(1000);
}
sh.Popup( "completed." );
そんでこっちがHTA側。名前はなんでもいいけど「test.hta」とかにしておく。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<title>test</title>
</head>
<body>
</body>
<script>
var ie;
window.onbeforeunload = function() {
ie.Quit();
}
setTimeout( function() {
ie = new ActiveXObject("InternetExplorer.Application");
var shell = new ActiveXObject("WScript.Shell");
var ws;
shell.Run( [ "cscript wsh.js ", ie.hWnd ].join("") );
for(var k = 1; k < 11; k++) {
ws = ie.GetProperty("WScript");
// wsh.jsからプロパティがセットされるとundefinedではなくなる。型は見なくてもOK(のハズ)
if( typeof( ws ) != "undefined" ) break;
shell.Run( "ping localhost -n 2", 0, true );
}
for(var i = 0; i < 10; i++) {
ws.echo( new Date() );
ws.WScript.Sleep( 1000 );
}
alert( ws.readln("なんか入力すれ。 > ") );
ws.quit = true;
window.close();
}, 0 );
</script>
</html>
IEに設定するプロパティ名が「WScript」になっているが、wsh.jsでセットしているオブジェクトはWScriptではなく、グローバルコードでの「this」になるので注意。
で、test.htaとwsh.jsを同じところに配置してtest.htaを起動すると、
- コンソール(cscript.exe)が起動して、1秒間隔で10回時間を表示する
- コンソールで入力を求められる
- HTA側でコンソールで入力された文字をalertして終了
- コンソール側もメッセージを表示して終了
簡単なライブラリ化をしてjsからHTAをダイアログのように使うのも試してみたので次のエントリで。
WScript - HTA 相互通信ライブラリ ― 2007年06月05日 20時40分45秒
ってほど大層なもんじゃないけど、まあこれも一種のIPCになるかなぁ、と思ったり。
scripthost.jsのソース
まずはライブラリコードから。これはwsh・htaの両方から共通で使用する。要:prototype.js
Object.extend( Enumerator.prototype, {
_each : function(iterator) {
this.moveFirst();
var i = 0;
try {
for(; ! this.atEnd(); this.moveNext()) {
try {
iterator( this.item(), i++ );
} catch(e) {
if( e != $continue ) throw e;
}
}
} catch(e) {
if( e != $break ) throw e;
}
}
} );
Object.extend( Enumerator.prototype, Enumerable );
var WshHost = Class.create();
WshHost.prototype = {
initialize : function(hWnd) {
if( isNaN( hWnd ) ) {
this._ie = new ActiveXObject("InternetExplorer.Application");
} else {
this._ie = new Enumerator( new ActiveXObject("Shell.Application").Windows() ).find( function(ie) {
return ie.hWnd == hWnd;
} );
if( ! this._ie ) throw new Error( "ie not found" );
}
this.id = this._ie.hWnd;
},
setHost : function(host) {
this._ie.PutProperty( "Host", host );
},
getHost : function() {
return this._ie.GetProperty( "Host" );
}
}
使い方
コンストラクタは役割によって使い分ける。先に起動しているほうは引数なしで実行、後で呼び出された側では、コマンドライン引数かなにかでIEのhWndを受け取って、それを引数として実行する。
次にホストオブジェクトを設定するほう(=wsh側だな)がsetHostメソッドを実行する。引数はHTA側に渡したいオブジェクトならなんでもよいが、「HTAからWScriptのグローバルコードへアクセスする」のであればグローバルな「this」を渡す。
受け取り側(HTA側)ではgetHostメソッドでホストオブジェクトを受け取る。
後は工夫次第でお互いに通信が可能になる。
サンプルコード(HTA)
WSHからダイアログ扱いされるHTAのサンプルを以下に示す。ファイル名は「test.hta」とする。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<meta http-equiv="Content-Style-Type" content="text/css">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<title>test</title>
<script src="prototype.js"></script>
<script src="scripthost.js"></script>
<script>
window.resizeTo( 450, 120 );
</script>
<hta:application id="hta"></hta>
<script>
// trimメソッド追加
String.prototype.trim = function() {
return this.replace( /^\s*/, "" ).replace( /\s$$/, "" );
}
// コマンドライン引数
this.Arguments = (function(src) {
var result = [];
var buf = {
_buf : [],
mode : false,
push : function(c) {
this._buf.push( c );
if( /["']/.test( c ) ) {
if( ! this.mode ) {
this.mode = true;
} else {
this.value = this._buf.slice(1, this._buf.length - 1).join("");
this.mode = false;
this._buf = [];
return false;
}
}
return true;
},
finish : function() {
if( this._buf.length == 0 ) return null;
return this._buf.join("");
}
};
for(var i = 0; i < src.length; i++) {
if( ! buf.push( src.charAt(i) ) ) {
result.push( buf.value );
}
}
result.push( buf.finish() );
return result.compact();
})( hta.commandLine.trim() );
</script>
<style>
</style>
<body>
<input type="file" id="file1" size="60">
<button id="btn1" disabled>確定</button>
</body>
<script>
// test.jsから渡されたhWndを引数にWshHostを初期化
var wshHost = new WshHost( this.Arguments[1] );
// ホストオブジェクト(=test.js内の'this')を取得
var host = wshHost.getHost();
Event.observe( $("file1"), "change", function() {
$("btn1").disabled = false;
}, false );
Event.observe( $("btn1"), "click", function() {
// test.jsのfileNameにパスをセット
host.fileName = $("file1").value;
// test.jsに終了を通知
host.quit = true;
// HTA自体も終了
window.close();
}, false );
</script>
</html>
コマンドライン引数の解析のコードがちょっとうざいが我慢して欲しい。
コードはそれほど複雑ではなく、input type=fileでファイルを選択して「確定」をクリックすると、呼び出し元のWSHにファイルパスと終了を通知して終了する。
サンプルコード(JS)
ちょっと長いので、ライブラリロードだの、echoだのreadlnだのの定義は省略し、肝心の部分のみ記載する。ロードするライブラリは「dummy.js」、「prototype.js」および今回の「scripthost.js」の3つ。dummy.jsはここらあたりのものを拾って使って欲しい。
ファイル名はこちらも単純に「test.js」としておこう。
var shell = new ActiveXObject("WScript.Shell");
var wshHost = new WshHost();
wshHost.setHost( this );
// HTAの終了待ち合わせフラグ
var quit = false;
// HTAで選択したファイルパスを受け取る変数
var fileName = null;
echo( "ファイルを選択してください。" );
shell.Run( [ "test.hta ", wshHost.id ].join("") );
while( ! quit ) {
WSH.Sleep( 100 );
}
echo( "選択されたファイル:", fileName );
で、実行すると
- test.jsを起動すると自動的にtest.htaが呼び出される
- test.htaでファイルを選択し、「確定」をクリックするとtest.htaは終了
- test.jsのコンソールにはhtaで選択したファイルのパスが表示される
サンプル一式
今回のサンプルコード一式を以下のリンクからダウンロードできるようにした。
prototype.jsも1.5.1を同梱している。prototype.js以外のjs、htaはnyslとする。
最近のコメント