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重引用符で~」みたいな話しか見つからない。
どっちが王道なのかなぁ。だれか教えてください。またはよい検索キーワード教えてください。
まぁ、それがわかったところでどうだという話ではないんだけどね。
最近のコメント