コンソールの入出力2007年05月08日 00時56分41秒

CScriptでの標準入出力

まあ、特に難しいことはないのだが、コンソールへの入出力+αのまとめってことで。

まず一番シンプルな「1行出力」。これは至極簡単で

WScript.Echo( "行出力" );
でよい。ただし、ホストがWScript.exeの場合はポップアップメッセージになるので注意。

次に入力だが、これはWScript#StdInオブジェクトを経由する。こんな感じ。

var input = WScript.StdIn.ReadLine();
WScript#StdInプロパティは、fsoのTextStreamオブジェクトとほぼ同等のメンバを備えているので他にReadメソッドも使えるが、結局Enterキーが必要になるので実質的にこのReadLineメソッドを使用することになる。ちなみにホストがWScript.exeの場合、StdInプロパティへのアクセスはエラーになるのでCScript実行時のみ使用すること。

改行を出力しない文字列出力は、WScript#StdOutプロパティを経由して、こんな感じで行う。

// 次の2行の実行で、
// 「WScript.StdOut.Write()」とひとつながりで出力される
WScript.StdOut.Write( "WScript." );
WScript.StdOut.Write( "StdOut.Write()\r\n" );
WScript#StdOutもTextStreamと同様に(出力専用だが)扱えるのでWriteLineメソッドも使用できる。この場合は、WScript#Echoと同様に改行込みで出力される。WScript実行時にエラーになるのはStdInと同様。

おまけになるが、CScript起動でもポップアップメッセージは使用できる。WScriptオブジェクトではなく、WshShellオブジェクトを使用する。

new ActiveXObject("WScript.Shell").Popup( "ポップアップです。" );
このコードはホストに依存しないので、WScriptだろうがCScriptだろうが、さらにはHTAだろうがWshShellオブジェクトをcreateできるなら実行可能。Popupメソッドは指定秒後に自動的に閉じるなど、WScript#Echoよりも機能があるのでリファレンスを確認してみると使い道がいろいろあるかも。

ユーティリティオブジェクトにまとめてみる

ここまでの説明で普通に入出力はできるが、なんとなくユーティリティオブジェクトにまとめたサンプルを載せてみる。 ついでに起動ディレクトリ(実行中のjsファイルがあるディレクトリ)などの情報もまとめて「JScript」オブジェクトをでっち上げてみた。

var JScript = {
	// WshShellのインスタンス
	shell : new ActiveXObject("WScript.Shell"),
	// 起動ディレクトリ
	startupPath : WScript.ScriptFullName.substr( 0, WScript.ScriptFullName.lastIndexOf("\\") ),
	// スクリプトファイル名
	name : WScript.ScriptName,
	console : {
		// 文字列出力
		print : function(str) {
			WScript.StdOut.Write( str || "" );
		},
		// 文字列の行出力
		println : function(str) {
			WScript.Echo( str || "" );
		},
		// 文字列入力
		readln : function() {
			return WScript.StdIn.ReadLine();
		},
		// ポップアップアラート(consoleじゃないけど)
		popup : function(str, title) {
			JScript.shell.Popup( str || "", 0, title || WScript.ScriptName );
		}
	}
}
// cscriptで起動しなおし
if( /wscript\.exe$/i.test( WScript.FullName ) ) {
	JScript.shell.Run( "cscript \"" + WScript.ScriptFullName + "\"" );
	WScript.Quit();
}

// println
JScript.console.println( "println()で出力" );

JScript.console.println();

// print
JScript.console.print( "print()で出力..." );
JScript.console.print( "しました。\r\n" );

JScript.console.println();

// startupPathとnameをprintlnで出力
with( {
	keys : [ "startupPath", "name" ],
	index : 0
} ) {
	do {
		JScript.console.println( keys[index] + " -> " + JScript[ keys[index] ] );
	} while( ++index < keys.length )
}

JScript.console.println();

// print + readln -> println
JScript.console.print( "なんか入力してください > " );
JScript.console.println( "あんたの入力 => " + JScript.console.readln() );

JScript.console.println();

// popup
JScript.console.popup( "おわり", "JScript.console テスト" );
コマンドライン引数の展開機能やWshShell#Execで外部プロセスを実行した結果を取得する機能などを追加したりするとちょっとしたライブラリになるかと。

EnumeratorをEnumerableに2007年05月08日 02時15分18秒

Enumeratorオブジェクト

ActiveXObjectと同様なJScript固有オブジェクトにEnumeratorがある。これはCOMのコレクションインターフェイスをJScriptから操作するためにある(と思われる)

Enumeratorオブジェクトは、

// folderはfsoのFolderオブジェクトとする
// folder#Filesはfolderが示すディレクトリ内のファイルコレクション
var list = new Enumerator( folder.Files );
for(; ! list.atEnd(); list.moveNext()) {
	// list#item()でコレクション要素(=fsoのFileオブジェクト)を取得
	var file = list.item();
	// あとはfileを操作する
}
のように、atEnd()メソッドがtrueを返すまでmoveNext()でループ処理を行い、コレクション要素を取得するのにitem()メソッドを使用する、という流れで使用するが、ちょっと独特のインターフェイスなためちょっと面倒に感じる。

Enumerableに拡張

以前のコマンドライン引数の展開ではラッパーオブジェクトを返す$E()関数を定義したが、今回は直接Enumerableにしてみる。

といっても別に面倒なことがあるわけではなく、_each()を実装後にEnumerableインターフェイスを追加するだけ。

// EnumeratorにEnumerableの機能を追加する
Enumerator.prototype._each = function(iterator) {
	this.moveFirst();
	for(; ! this.atEnd(); this.moveNext()) {
		iterator( this.item() );
	}
}
Object.extend( Enumerator.prototype, Enumerable );
こうして拡張されたEnumeratorを使用して、C:\直下のファイルを列挙するサンプルは以下のようになる。JScript.consoleは前回のエントリで試したオブジェクトとする。
new Enumerator( JScript.fso.GetFolder( "C:\\" ).Files ).each( function(file, index) {
	JScript.console.println( index + " - " + file.Name );
} );
この方法なら、コレクションを配列にキャッシュしないので、たとえば1,000単位や10,000単位のファイルがあるディレクトリの操作でもたいしてメモリを消費せずにすむ(って前も書いたな)。

サンプルコード

まとめとして、前回のエントリの定義+ライブラリロードを加えたサンプルを以下に示す。dummy.jsとprototype.jsは同じディレクトリにあるものとしている。

var JScript = {
	// WshShellのインスタンス
	shell : new ActiveXObject("WScript.Shell"),
	// fsoのインスタンス
	fso : new ActiveXObject("Scripting.FileSystemObject"),
	// 起動ディレクトリ
	startupPath : WScript.ScriptFullName.substr( 0, WScript.ScriptFullName.lastIndexOf("\\") ),
	// スクリプトファイル名
	name : WScript.ScriptName,
	console : {
		// 文字列出力
		print : function(str) {
			WScript.StdOut.Write( str || "" );
		},
		// 文字列の行出力
		println : function(str) {
			WScript.Echo( str || "" );
		},
		// 文字列入力
		readln : function() {
			return WScript.StdIn.ReadLine();
		},
		// ポップアップアラート(consoleじゃないけど)
		popup : function(str, title) {
			JScript.shell.Popup( str || "", 0, title || WScript.ScriptName );
		}
	},
	// 2つのパスを結合するヘルパメソッド
	buildPath : function(path1, path2) {
		return [ path1, path2 ].join( /[\\\/]$/.test( path1 ) ? "" : "/" );
	}
}
// cscriptで起動しなおし
if( /wscript\.exe$/i.test( WScript.FullName ) ) {
	JScript.shell.Run( "cscript \"" + WScript.ScriptFullName + "\"" );
	WScript.Quit();
}

// ライブラリロード
with( {
	libs : [
		"dummy.js",
		"prototype.js"
	],
	index : 0,
	getSource : function(lib) {
		var xhr = new ActiveXObject("Microsoft.XMLHTTP");
		var stream = new ActiveXObject("ADODB.Stream");
		try {
			xhr.open( "GET", "file://" + JScript.buildPath( JScript.startupPath, lib ), false );
			xhr.send();
			
			stream.Open();
			stream.Type = 1; // open as binary mode
			stream.Write( xhr.responseBody );
			
			stream.Position = 0; // rewind
			stream.Type = 2; // change to text mode
			stream.Charset = "Shift_JIS";
			
			return stream.ReadText();
		} finally {
			if( stream ) {
				stream.Close();
			}
		}
	},
	path : ""
} ) {
	while( index < libs.length ) {
		path = libs[ index++ ];
		try {
			eval( getSource( path ) );
		} catch(e) {
			JScript.console.println( "ERROR on " + path );
			JScript.console.println( e.description );
		}
	}
}

// EnumeratorにEnumerableの機能を追加する
Enumerator.prototype._each = function(iterator) {
	this.moveFirst();
	for(; ! this.atEnd(); this.moveNext()) {
		iterator( this.item() );
	}
}
Object.extend( Enumerator.prototype, Enumerable );

// C:直下のファイル名をeachで列挙
new Enumerator( JScript.fso.GetFolder( "C:\\" ).Files ).each( function(file, index) {
	JScript.console.println( index + " - " + file.Name );
} );

JScript.console.print( "Enterキーで終了 > " );
JScript.console.readln();
arguments#calleeを使用した再起処理なんかもできるので、個人的には結構お勧め。 prototype.jsをインクルードできればよいので、当然HTAでも使用できる。

今日の気になったもの2007年05月08日 16時07分06秒

Entrance

Java製のMySQLフロントエンド。ドラッグドロップによるテーブル移動はともかく、グラフ機能が楽しそうだ。要JRE1.6。

Sansa Shaker

これは面白いインターフェイスだ。ちょっとだけ欲しい。

JScript内でVBScriptを実行する2007年05月08日 20時15分59秒

こんなに簡単にできるの?

Kanegon's Web Pageというサイトで、Script Controlというコンポーネントの存在を知った。

これはVBScriptやJScriptなどのActiveScriptを実行するためのActiveXコントロールで、以前から存在は知っていたが、UIコンテナがない環境でもサクっと使用できると知り目から鱗が落ちた。

ちょっとコードなど

論より証拠、JScriptからVBScriptを実行するサンプルを以下に示す。

// VBSのTypeName関数で、ActiveXObjectの型の名前を返す関数を定義する
var sc = new ActiveXObject("ScriptControl");
sc.Language = "VBScript";
sc.AddCode( "Function GetTypeName(obj) : GetTypeName = TypeName( obj ) : End Function" );

// XHRの型名を取得 → IXMLHTTPRequest
WScript.Echo( sc.Run( "GetTypeName", new ActiveXObject("Microsoft.XMLHTTP") ) );

// XMLDOMDocumentの型名を取得 → DOMDocument
WScript.Echo( sc.Run( "GetTypeName", new ActiveXObject("MSXML.DOMDocument") ) );

// IEの型名を取得 → IWebBrowser2
var ie = new ActiveXObject("InternetExplorer.Application");
WScript.Echo( sc.Run( "GetTypeName", ie ) );
ie.Quit();

最初の3行の定義をするだけで、JScript単体では不可能な「ActiveXオブジェクトの型の名前を取得する」という機能をVBSの力を借りて実現できる。

これで何がうれしいの?

VBSにあってJSにない機能というのはいくつかあるが、特にブラウザ上でのpromptに相当するInputBox関数は使える局面があると思う。そのほか、上記のサイトではADODB.Streamのバイナリデータの参照にVBSを経由させる方法を紹介していた。

そんだけといえば、そんだけだけど。

上記サイトではこのテクニックの他にJScriptでExcelなどを扱った後にGCを強制発動させる隠しメソッド「CollectGarbage」なんかについても言及されている。「覚書とか」の「JavaScript メモ」と「スクリプトの文字コード処理に関する覚書」は非常に役立つと思う。