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 );
}
}
}
たぶん、こんなんでよろこんでるの俺だけだろうなぁ。
NaNの検出 ― 2007年05月11日 22時19分45秒
知らんかったわぁ
たしかはてブ経由だと思ったが、集積蔵 - NaNを見つける役に立たないテクニックほかという記事を見つけた。いやぁ、「( NaN == NaN ) == false」とは知らんかった!!
2を使いたい誘惑に駆られるけど、さすがにアレなのでと書かれているが、dara-j的には2でしょう!これ、かっこいいよ。
他にも文字列足したり、Infinity足したり、いろいろ考えつくなぁ、と感心することしきり。
蛇足
しかし、id:trickstar_osさんは
といいたいところなんですが、このページ(はてな)でやるとprototype.jsのArray拡張がうざい。。とおっしゃっておられますが、prototype.jsあるおかげで
[ 1, NaN, "1", "a1", null, undefined, -Infinity ].each( function(a) { console.log( [ a, a + "", a + Infinity, isNaN(a) && typeof a == "number", a != a, a + "" == "NaN" ] ); } )
みたいにワンライナー(ちょっと長いか)で書けるところがよいと思うんだけどなぁ。
引用符について ― 2007年05月11日 22時31分57秒
このエントリは中身がほとんどありません。でもちょっと気になってる。
他の人が書いたコードとか見ていると、わりと
var s = 'string';と文字列リテラルを単一引用符で囲っているのを見かける。
dara-jはよほどのことがない限り
var s = "string";と2重引用符を使用している。これはなんとなく意地に近いものがあり、
var attr = "bgcolor='red'";とすればいいものをわざわざ
var attr = "bgcolor=\"red\"";とエスケープまでしてしまう。これはこれで可読性を落とすのでどうかと自分でも思うが。
どっちが王道なのかと思い検索はしてみるものの、「単一引用符または2重引用符で~」みたいな話しか見つからない。
どっちが王道なのかなぁ。だれか教えてください。またはよい検索キーワード教えてください。
まぁ、それがわかったところでどうだという話ではないんだけどね。

最近のコメント