VBScriptラッパー2007年05月11日 21時27分31秒

やっぱメソッドを文字列で指定するのはねぇ。。。

以前のエントリで、J(ava)?ScriptからVBScriptのコードを実行する方法を紹介したが、やはり

vb.Run( "VbFunc", param1, param2 )
とかって、文字列ベースで呼び出すのはなんとなく落ち着かない。さらに、JavaScript使ってるのに引数の数が束縛されるのも個人的には落ち着かない。 なので、ラッパーを作ってみることにした。

prototype.jsと、String.format、さらにEnumeratorのEnumerable化が前提だが、こんな感じ。

// VBScript実行エンジンラッパークラス
var VBScript = Class.create();
VBScript.prototype = {
	initialize : function() {
		this._vbe = new ActiveXObject("ScriptControl");
		this._vbe.Language = "VBScript";
	},
	addFunction : function(code) {
		this._vbe.AddCode( code );
		this._copyMembers();
	},
	reset : function() {
		this._vbe.reset();
		[ "initialize", "addFunction", "reset", "_copyMembers" ].each( function(key) {
			delete( this[ key ] );
		} );
	},
	_copyMembers : function() {
		var self = this;
		var vbe = this._vbe;
		
		new Enumerator( vbe.Procedures ).each( function(proc) {
			self[ proc.Name ] = function() {
				var args = arguments;
				var p = $R( 0, proc.NumArgs, true ).map( function(i) {
					return args[i];
				} );
				return eval( "vbe.Run( \"{0}\", {1} )".format(
					proc.Name,
					p.map( function(obj, i) { return "p[{0}]".format( i ); } ).join(", ")
				) );
			}.bind( self );
		} );
	}
}
ごめんなさい。クラス名がベタなのは勘弁してください。

使い方

  • インスタンスの生成

    パラメータなしでnewするだけ。
    var vb = new VBScript;
    
  • 関数・プロシージャの追加

    addFunctionメソッドを使用する。パラメータにはVBSコードを直接渡す。
    vb.addFunction( "Function prompt(msg, title) : prompt = InputBox(msg, title) : End Function" );
    
    この例では、Functionプロシージャを1つ登録しているだけだが、1回のaddFunction()で一気に複数のプロシージャを食わせることができる。まだ試していないが、.vbsファイルを読み込んで直接流し込むことができると思う。
  • 関数・プロシージャの呼び出し

    以下のように、VBSコードで定義したプロシージャ名・関数名をそのままVBScriptインスタンスのメソッド名として呼び出せる。
    // VBS関数名で直接呼び出せる
    vb.prompt( "なんか入力すれ。", "VBS テスト" );
    
    VBSの定義より引数が多い場合は自動的に切り詰められ、少ない場合はundefined(多分VBSではNothing扱い)が渡されるので、引数の数によるエラーは発生しない。 ちなみに、VBS側がSubプロシージャで登録されていた場合、実行結果は当然undefinedになる。

注意点とか、制限とか

  • プロシージャ名にVBSの組み込み関数名を使用してはいけない。例えば「Function msgBox : msgBox = MsgBox~」のようなコードを与えると、無限再帰呼び出しになってコールスタックを食いつぶすので注意(VBSはcase-sensitivityではないのでこれまた注意!)
  • VBS側で参照可能な変数を与える方法がわからない。多分addFunction()に渡すコード中に含めればよいが、その変数をJS側で知覚する術がない。
  • VBScriptクラスのメンバ名と同じプロシージャ名を使用してはいけない。原理的にはVBS実行環境内のプロシージャオブジェクトを列挙して、その名前をVBScriptインスタンスのメソッド名に流用しているため、「addFunction」なんて名前のプロシージャを定義したコードを食わせるとそれ以降VBSコードの追加ができない。

最後に長いサンプルを

いろいろとライブラリ依存になっているため、VBScriptクラスのコードだけ載せても試せないから、過去のエントリからかき集めてマージしたサンプルコードを示す。あ、dummy.jsとprototype.jsは同じディレクトリに設置してください。

// CScriptで起動しなおし
if( /wscript\.exe/i.test( WSH.FullName ) ) {
	new ActiveXObject( "WScript.Shell" ).Run( "cmd /k cscript //nologo \"" + WSH.ScriptFullName + "\"" );
	WSH.Quit();
}

// ライブラリロード処理
with( {
	// ライブラリのパスリスト
	libs : [
		"dummy.js",
		"prototype.js"
	],
	// libs[]のインデックス
	index : 0,
	// ソース取得メソッド
	getSource : function(url) {
		var stream = new ActiveXObject("ADODB.Stream");
		var xhr = new ActiveXObject("Microsoft.XMLHTTP");
		try {
			xhr.open( "GET", url, false );
			xhr.send();
			
			stream.Open();
			stream.Type = 1; // バイナリモード
			stream.Write( xhr.responseBody ); 
			
			stream.Position = 0; // 読み取り位置を巻き戻す
			stream.Type = 2; // テキストモードに変更
			stream.Charset = "_autodetect";
			
			return stream.ReadText();
		} finally {
			stream.Close();
		}
	}
} ) {
	while( index < libs.length ) {
		try {
			eval( getSource( libs[ index++ ] ) );
		} catch(e) {
			WScript.Echo( e.description || e.message || "error" );
		}
	}
}

// String.format
String.format = function() {
	var args = [];
	for(var i = 0; i < arguments.length; i++) args[i] = arguments[i];
	var format = args.shift();
	
	var reg = /\{((\d)|([1-9]\d+))\}/g;
	return format.replace( reg, function() {
		var index = Number( arguments[1] );
		var result = args[ index ];
		if( typeof( result ) == "undefined" )
			throw new Error( "arguments[ " + index + " ] is undefined." );
		
		return result;
	} );
}
String.prototype.format = function() {
	var args = [];
	for(var i = 0; i < arguments.length; i++) args[i] = arguments[i];
	return String.format.apply( String, [ this ].concat( args ) );
}

// EnumeratorをEnumerableに拡張
Enumerator.prototype._each = function(iterator) {
	this.moveFirst();
	for(; ! this.atEnd(); this.moveNext()) {
		iterator( this.item() );
	}
}
Object.extend( Enumerator.prototype, Enumerable );

// VBScript実行エンジンラッパークラス
var VBScript = Class.create();
VBScript.prototype = {
	initialize : function() {
		this._vbe = new ActiveXObject("ScriptControl");
		this._vbe.Language = "VBScript";
	},
	addFunction : function(code) {
		this._vbe.AddCode( code );
		this._copyMembers();
	},
	reset : function() {
		this._vbe.reset();
		[ "initialize", "addFunction", "reset", "_copyMembers" ].each( function(key) {
			delete( this[ key ] );
		} );
	},
	_copyMembers : function() {
		var self = this;
		var vbe = this._vbe;
		
		new Enumerator( vbe.Procedures ).each( function(proc) {
			self[ proc.Name ] = function() {
				var args = arguments;
				var p = $R( 0, proc.NumArgs, true ).map( function(i) {
					return args[i];
				} );
				return eval( "vbe.Run( \"{0}\", {1} )".format(
					proc.Name,
					p.map( function(obj, i) { return "p[{0}]".format( i ); } ).join(", ")
				) );
			}.bind( self );
		} );
	}
}

with( {
	$_A : function( args ) {
		var a = [];
		for( var i = 0; i < args.length; i++ ) a.push( args[ i ] );
		return a;
	},
	_p : "js>",
	_print : function() {
		var a = arguments;
		for( var i = 0; i < a.length; i++ ) WSH.StdOut.Write( a[ i ] );
	},
	_in : "",
	print : function() {
		WSH.Echo( this.$_A( arguments ).join( "" ) );
	},
	read : function(m) {
		if( m ) this._print( m );
		return WSH.StdIn.ReadLine();
	},
	vb : new VBScript
} ) {
	vb.addFunction( [
		"Function getTypeName(obj)",
			"getTypeName = TypeName( obj )",
		"End Function",
		
		"Function prompt(msg, title, default)",
			"prompt = InputBox( msg, title, default )",
		"End Function",
		
		"Function yesNo(msg, title)",
			"yesNo = MsgBox( msg, vbYesNo, title )",
		"End Function"
	].join("\r\n") );
	while( ( _in = read( _p ) ) != "exit" ) {
		try {
			print( eval( _in ) );
		} catch( e ) {
			print( e.description );
		}
	}
}
たぶん、こんなんでよろこんでるの俺だけだろうなぁ。

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※なお、送られたコメントはブログの管理者が確認するまで公開されません。

名前:
メールアドレス:
URL:
コメント:

トラックバック

このエントリのトラックバックURL: http://dara-j.asablo.jp/blog/2007/05/11/1501399/tb

※なお、送られたトラックバックはブログの管理者が確認するまで公開されません。