オブジェクトブラウザ代わり ― 2007年06月03日 19時15分14秒
オブジェクトブラウザって便利だけど
WSHなんかでCOMを扱うとき、OfficeかVB6をインストールしてるなら、オブジェクトブラウザが役に立つ。VC++があるならOLE Viewerもいいだろう。
でもこれらのツールは無償ではないので、VB6なりVC++なりもっていない場合はどうするかっていうお話。いくつか探してみた。
COM Explorer
COM Explorer - Explore, Manage and Repair ActiveX Controls, DLL & EXE Servers
シングルライセンス $150の商用ソフト。VBA/VB6のオブジェクトブラウザよりは多分高機能。30days trialがあるみたい。
Simple OLE Browser
Win32OLE の製作過程よりDL可能
Ruby製のツール。Rubyインストールしてあるならこれでよいかと。動作もさくさくしてるし。
tlbimp/aximp
.NET Framework SDKをインストールするといっしょに入るコマンドラインユーティリティ。COMオブジェクトの.NETラッパーアセンブリを生成してくれる。使い方はSDKドキュメントを参照。
で、生成したアセンブリをVSのプロジェクトで参照するなりReflectorに喰わせるなりすればOK。
が、これはCOMのインターフェイスを提供してるDLL/EXEを知らないと使えないのであまりおすすめではないか。
VB/VC# 2005 Express Edition
たぶんこれ最強。なんかてきとーにプロジェクト作って、参照設定でCOMから追加すればOK。VBA/VB6のオブジェクトブラウザよろしく、インストールされているライブラリの一覧から選択して参照を追加するので、ライブラリの名前からあたりつけて必要な機能探したりとかもできる。
もちろん.NETアセンブリが作成されるのでReflector行きもアリ。
tlbimp/aximpで作成してもそうだけど、VBA/VB6のオブジェクトブラウザと違い、クラス/インターフェイスの継承関係も調べられるのでちょっと便利度が高いと思う。
VBScriptラッパー修正 ― 2007年06月04日 02時58分34秒
意味不明メソッドとちょっとしたバグ修正
以前のエントリに掲載した、JScriptからVBSを実行するためのVBSラッパーだが、よくよく見直してみるとちょっと具合が悪かった。
まずresetメソッドを実装していたのだが、自分で見直しても用途が不明な上に、たぶん実装時に意図していた動作は行わないんじゃなかろうかということに気づいた。なのでこのメソッドは廃止する。
それから、追加するVBSのプロシージャ/関数で引数を取らないものを登録したときに、ラッパーから実行するとエラーになっていたので、VBSプロシージャ/関数の引数の数をチェックするようにした。
修正済みコード
以下のようになる。組み込み方/使い方は以前のエントリを参照のこと。
// 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(); }, _copyMembers : function() { var self = this; var vbe = this._vbe; new Enumerator( vbe.Procedures ).each( function(proc) { self[ proc.Name ] = function() { if( proc.NumArgs == 0 ) { return vbe.Run( proc.Name ); } 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 ); } ); } }
便利なVBSコード
おまけになるが、JScript単体では困難で、登録しておくと便利なVBSコードスニペットをいくつか書いておく。
前提で、VBSラッパーをvbsという名前でnewしておこう。
var vbs = new VBScript();
まずはInputBoxラッパー。ブラウザ上のpromptっぽい使い方をするので「prompt」として登録する。
vbs.addFunction( [ "Function prompt(msg, title)", " prompt = InputBox(msg, title)", "End Function" ].join("\r\n") );引数「msg」はダイアログに表示されるメッセージ、titleはダイアログのタイトルになる。InputBoxはボタンやアイコンの指定なんかもできるのだが、そこまで複雑にしなくてもいいやという投げやりな実装である。
次に何度か取り上げているが、TypeName関数のラッパー。
vbs.addFunction( [ "Function getTypeName(obj)", " getTypeName = TypeName( obj )", "End Function" ].join("\r\n") );objの型名を返してくれる。typeof演算子と違い、COMオブジェクトの型を調べられる。
最後に、役に立つケースは極めてまれだが、VBSのNothingを得るgetNothingを実装する。詳細はこちらのサイトが非常に詳しいが、VBSのNothingはnullでもundefinedでもない。COMを扱っているとまれにNothingが必要になるので、そんな場合に役立つかと。
vbs.addFunction([ "Function getNothing", " Set getNothing = Nothing", "End Function" ].join("\r\n") );
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とする。NanoBookほすぃ ― 2007年06月06日 23時58分28秒
VIA NanoBook ウルトラモバイルノート - Engadget Japanese
これ、いいわ。
NanoBookと名付けられたリファレンスデザインは7インチWVGAディスプレイにフルQWERTYキーボードを搭載したミニノートPCで、 1.2GHz C7-Mプロセッサ・1GB RAM・30GB HDDを載せてWindows XPまたはVistaが走ります。で且つ
売りは23cm x 17cm程度のサイズと約850gという軽さ、ターゲット$600という低価格。バッテリー駆動時間も約4.5時間とそれなり。そのほかスペックは802.11g 無線LAN、Bluetooth、DVI出力、メモリカードスロットなど。って、すげーほしい。ほんとに$600なら。バッテリ持ちはもう少し悪くてもいいす。
科学ニュースあらかるとさんでも記事になってるなぁ。よいなぁ。
追記:これ見逃してたわ。
それすらどうでも良くなりそうな魅惑のオーラを放っているのがメインディスプレイ右側にある謎の表示部分。どうやらここは独自の汎用スロットで、WWANデータ通信やGPS、VoIPといったモジュールを挿して使うことを想定しているようです。いや、他のモジュール割とどうでもいいや。写真の時計で十分かっこいいし。
最近のコメント