goo blog サービス終了のお知らせ 

JScript について

2012-04-28 08:29:06 | Programming > JScript
はじめに
一般的に JavaScript はウェブブラウザで利用されるスクリプト言語という認識だと思うが、Microsoft 版の JScript は Windows 上で Active Scripting エンジンとして実装されており、WSH などで Windows プログラミングの言語としても利用される。例えば ActiveXObject というコンストラクタを用いて COM コンポーネントを利用することができるが、もちろんこれは JScript の独自拡張である。

JScript を使うにあたり、JavaScript(ECMAScript) との違いを把握しておくことは重要である。厄介なのは JScript が ECMAScript 準拠ではなく、あくまで「ベースにしている」に留まっているという点である。JavaScript としてコーディングする際、JScript だけがおかしな挙動を示すことは珍しくない。ウェブブラウザでの実行を前提とした場合、処理系の違いを吸収するためクロスブラウザを意識することになるが、JScript と JavaScript の違いを把握しておかないとコーディングに苦労することとなるだろう。両者はある程度切り分けて考えておくべきだというのが私の考えである。

ここでは主に Windows プログラミングとしての JScript を扱う。また一般的な JavaScript との違いについても言及していきたい。あくまで趣味範囲での記事内容であり、その正確さについては正式な文書の参照、あるいは各自の環境における動作確認を優先させていただきたい。


もくじ
JScript 用 Include コンポーネント
・JScript ファイルに一行加え、インクルードを実現する方法
・JScript で文字列内の変数を展開する関数を作ってみた
・JScript で関数を動的スコープ化する関数を作ってみた


SFC mini 拡張セット (近日公開予定)
・JScript で Windows API を利用するためのモジュールセット
・機能拡張版 SFC mini (sfcmini.dll) + モジュール (sfcmini.js)


COM コンポーネント用モジュール (近日公開予定)
・JScript から ActiveX コンポーネントではないCOM コンポーネント (IDispatch インターフェイス未実装) を利用する
・モジュール (comobj.js)

JScript で関数を動的スコープ化する関数を作ってみた

2012-04-21 19:13:14 | Programming > JScript
はじめに
スクリプト コンポーネントが持つスコープの独立性を活かし、ユーザー定義関数を動的スコープ化する関数を作ってみた。


Include コンポーネント
基本的な機能の紹介やダウンロード先は Include コンポーネントのページを参照のこと。

構文
Include コンポーネントをアクティブ化することで $local という動的スコープ化用の関数が使えるようになる。$local は関数オブジェクトを受け取り、動的スコープ化した関数オブジェクトを返す。
($=new ActiveXObject('JScript.Include'))(this);

function fn1(p) {
	return p+'->'+a;
};

var fn2 = $local(fn1);

function scopeA() {
	var a = 'scopeA';
	WScript.echo(fn1('fn1: scopeA'));
	WScript.echo(fn2('fn2: scopeA'));
	(function scopeB() {
		WScript.echo(fn1('fn1: scopeB'));
		WScript.echo(fn2('fn2: scopeB'));
	})();
}

var a = 'global';
WScript.echo(fn1('fn1: global'));
WScript.echo(fn2('fn2: global'));
scopeA();
上記は関数内から変数 a の値を取得するサンプルコードである。fn1、fn2 ともに同一のコードであるが、fn1 は常に "global" を返すのに対し、fn2 は動的スコープになっている。つまり fn2 は呼び出し側のスコープを参照可能ということである。動的スコープの概念についてはこちら


JSCript > Include コンポーネント

JScript で文字列内の変数を展開する関数を作ってみた

2012-04-20 21:11:21 | Programming > JScript
はじめに
文字列の連結が込み入ってくるとソースコードの可読性が落ちる。
var url = 'http://' + hostname + '/image/' + id + '.cgi?w=' + x + '&h=' + y;
こういった記述法は連結後の文字列がイメージしにくいのが欠点である。JScript の用途からすれば、やはり見やすさも考慮しておきたい。例えば Perl では以下のように文字列に変数を埋め込み、それを展開させることができる。
http://${hostname}/image/${id}.cgi?w=${x}&h=${y}
直感的なわかりやすさではこちらのほうが上である。$q は JScript でこれを実現するための関数である。


Include コンポーネント
基本的な機能の紹介やダウンロード先は Include コンポーネントのページを参照のこと。

構文
Include コンポーネントをアクティブ化することで $q という変数展開用の関数が使えるようになる。
($=new ActiveXObject('JScript.Include'))(this);
var url = $q('http://${hostname}/image/${id}.cgi?w=${x}&h=${y}');

上記の場合、$q を呼び出したコンテキストにおいて hostname, id, x, y という変数の値が参照され、文字列中の変数部分が置き換えられる。未定義の変数はエラーとなるので注意。


パース
添え字にも対応できるよう変数のパースは緩めに作ってある(実際はコードとして認識できれば変数以外でも可)。
($=new ActiveXObject('JScript.Include'))(this);
var url = $q('http://${hostnames[0]}/image/${idlist[i]}.cgi?w=${rect[0]}&h=${rect[1]}');


スクリプト コントロール化が必要な理由
なぜスクリプト コントロール化が必要なのか。まずは Include コンポーネントに実装されている $q 関数をスクリプト上で定義した場合、どのような挙動を示すか確認してみる。
function $q(str) {
	return str.replace(/\${([^{}]+)}/g, function ($0, $1) {
		return eval($1);
	});
}

function scopeA() {
	var a = 'scopeA';
	var txt = $q('scopeA の変数 a の値は ${a} です。');
	WScript.echo(txt);
}

var a = 'global';
var txt = $q('global の変数 a の値は ${a} です。');
WScript.echo(txt);
scopeA();
このコードを実行すると変数 a の値はいずれも "global" となる。$q 関数はグローバル コンテキスト上で定義されているため、そのスコープチェーン(変数オブジェクトのリスト)は常に「$q 関数の Activation オブジェクト -> グローバル オブジェクト」となる。scopeA 内で $q 関数が呼ばれた場合、$q 関数の eval で参照してほしいのは scopeA のローカル変数であるが、scopeA の Activation オブジェクトは $q 関数のスコープチェーンに含まれないため、$q 関数が scopeA のローカル変数を参照することはない。ではどうするか。
function $q(str) {
	return str.replace(/\${([^{}]+)}/g, function ($0, $1) {
		return eval($1);
	});
}

function scopeA() {
	eval($q.toString());
	var a = 'scopeA';
	var txt = $q('scopeA の変数 a の値は ${a} です。');
	WScript.echo(txt);
}

var a = 'global';
var txt = $q('global の変数 a の値は ${a} です。');
WScript.echo(txt);
scopeA();
上記は scopeA 内で eval を使って $q を再定義する一行を追加し、スコープチェーンに scopeA が含まれるようにしたものである。こうすれば一応期待通りの動作は得られる。関数ごとに $q を再定義というのはコーディングとしてスマートではないが、JScript だけで実現しようとするとこうする他ない。そこでスクリプト コンポーネントを利用する。
($=new ActiveXObject('JScript.Include'))(this);

function scopeA() {
	var a = 'scopeA';
	var txt = $q('scopeA の変数 a の値は ${a} です。');
	WScript.echo(txt);
}

var a = 'global';
var txt = $q('global の変数 a の値は ${a} です。');
WScript.echo(txt);
scopeA();
$q 関数をコンポーネント側で定義し、それをスクリプト側から呼び出すと $q 内の eval が辿るスコープチェーンはコンポーネント側の $q のスコープチェーンではなく、呼び出し元コンテキストである scopeA のスコープチェーンとなる。これはコンポーネント側のスコープチェーンがスクリプト側のそれとは独立していることに起因する。つまり $q 関数はこの仕様を逆手にとり、動的スコープを実現している。スコープの独立性という点からすれば、スクリプト コントロールを使っても同様のことができると思われるが、コーディングの煩雑さを考えれば、あらかじめスクリプト コンポーネントを使ってモジュール化しておくほうが得策だろう。


JSCript > Include コンポーネント

JScript 用 Include コンポーネント

2012-04-18 23:39:03 | Programming > JScript
はじめに
Include コンポーネントは JScript 上では関数化が困難とされる機能を提供する言語仕様拡張系のスクリプト コンポーネントである。通常のコンポーネントとは異なり、オブジェクトとして公開されることはなく、呼び出し元 JScript の変数に直接オブジェクトの関数を割り当てるという特殊な形態をとっている。


機能紹介
現在 Include コンポーネントに実装されている機能は以下の通り。

1) 他の JScript ファイルをインクルード
変数 $include に他の JScript ファイルをインクルードする関数を割り当てる。
詳細:JScript ファイルに一行加え、インクルードを実現する方法

2) 文字列内の変数展開
変数 $q に文字列内の変数展開を行う関数を割り当てる。
詳細:JScript で文字列内の変数を展開する関数を作ってみた

3) 関数の動的スコープ化
変数 $local にユーザー定義関数を動的スコープ化する関数を割り当てる。
詳細:JScript で関数を動的スコープ化する関数を作ってみた

各関数の詳細はリンク先を参照のこと。


ダウンロード
include.zip


インストール
通常スクリプト コンポーネントはシステムに登録して利用する。手順としてはエクスプローラ上で include.wsc を右クリックし、[登録] を実行する(または regsvr32.exe を使用してコマンドラインから登録)。登録後に include.wsc を移動するとコンポーネントが動作しなくなるので注意。その場合は移動先で再度 [登録] を実行すること。後述するが、構文によっては登録なしで呼び出すことも可能である。


構文
($=new ActiveXObject('JScript.Include'))(this);
上記は include.wsc をコンポーネントとしてシステムに登録した場合に利用できる構文である。一時的に変数$を利用するが、最終的に代入は行われない(後述)。システムへの事前登録を行わない場合は下記のように include.wsc のフルパスを指定して呼び出すことも可能(モニカによる参照)。
($=GetObject('script:C:\\...\\include.wsc'))(this);
いずれかの構文を用いてコンポーネントをアクティブ化することにより、Include コンポーネントに実装された機能を JScript の関数として呼び出すことができるようになる。この構文にはいくつかのオプションがあるが、それらは各機能の項で説明する。


構文の仕組み
構文はシンプルかつ一行で書けるよう工夫したもので、以下のような流れとなっている。
$ = new ActiveXObject('JScript.Include');
$.Initialize(this);
this['@Raptor'] = $;
delete $;
ActiveXObject オブジェクトのロード状態を保つため、オブジェクトの参照は必須である。スクリプト コンポーネント内から暗黙的に参照することはできないため(コンポーネント内の this は別物)、一旦 $ プロパティに格納する。その後、Initialize メソッドで @Raptor プロパティに入れなおし、$ プロパティを削除する。直接 this['@Raptor'] を使わず $ プロパティを経由しているのは構文をシンプルにしたかったため。また @Raptor といった添字アクセスしかできないプロパティを用いることで変数名の衝突を避けた。Initialize メソッドはデフォルトプロパティとして定義しているので省略可能。これらの組み合わせで構文は作られている。


スクリプト コンポーネントについて
スクリプト コンポーネントは XML フォーマットのテキスト ファイルであり、これ自体が COM コンポーネントとして動作する。実装には JScript や VBScript といったスクリプト言語が使われ、一言で言うなら「メモ帳で書ける COM コンポーネント」である。わずか数十行のコードなので是非読んでみてほしい。


おわりに
本コンポーネントが JScript プログラマの一助となれば幸いです。感想・不具合報告などお待ちしております。


JScript > Top (作成中……)

JScript ファイルに一行加え、インクルードを実現する方法

2012-04-14 08:57:35 | Programming > JScript
はじめに
JScript にはスクリプト単体でインクルードが可能な関数やステートメントは提供されていない。そこでスクリプト コンポーネントを用いて、これを実現する方法を考えてみた。たった一行加えるだけのシンプルなコードである。JScript の eval が抱える実行コンテキストの問題も解決している。興味のある方は是非一度試していただきたい。

※現在は Include コンポーネントの機能の一つとして公開しています。

Include コンポーネント
基本的な機能の紹介やダウンロード先は Include コンポーネントのページを参照のこと。

構文
Include コンポーネントをアクティブ化することで $include というインクルード用の関数が使えるようになる。
($=new ActiveXObject('JScript.Include'))(this);
$include('C:\...\js\module1.js');
$include('C:\...\js\module2.js');
$include('C:\...\js\module3.js');


文字コードの指定
インクルードされる JScript ファイル(以下 "モジュール" )は UTF-8 として読み込まれる。それ以外の文字コードで書かれている場合は第二引数で指定する。"_autodetect" は自動検出の指定だが、誤判定の可能性もあるのであまりお勧めしない。
$include('C:\\...\\js\\module1.js', 'shift_jis');
$include('C:\\...\\js\\module2.js', 'euc-jp');
$include('C:\\...\\js\\module3.js', '_autodetect');
モジュールのデフォルト文字コードを変更したい場合は前述の構文において、第二引数以降にそのパスを指定する。
($=new ActiveXObject('JScript.Include'))(this, 'shift_jis');
$include('C:\\...\\js\\module1.js');
$include('C:\\...\\js\\module2.js');
$include('C:\\...\\js\\module3.js');
さらに個別指定しても構わない。
($=new ActiveXObject('JScript.Include'))(this, 'shift_jis');
$include('C:\\...\\js\\module1.js');
$include('C:\\...\\js\\module2.js', 'euc-jp');
$include('C:\\...\\js\\module3.js', 'utf-8');


パスの指定
モジュールのパスは絶対パスによる指定が基本だが、相対パスで指定した場合は呼び出し元 JScript ファイルが置かれたディレクトリを基準としてパスを補完する(スクリプト ホストが Windows Script Host の場合のみ)。すべてのモジュールが同一ディレクトリにあるなら
($=new ActiveXObject('JScript.Include'))(this);
$include('module1.js');
$include('module2.js');
$include('module3.js');
もちろん「.」や「..」、環境変数も使える。
($=new ActiveXObject('JScript.Include'))(this);
$include('.\\module1.js');
$include('..\\module2.js');
$include('%USERPROFILE%\\My Documents\\js\\module3.js');
セパレータに "/" を使ってもよい。このあたりはお好みで。
($=new ActiveXObject('JScript.Include'))(this);
$include('./module1.js');
$include('../module2.js');
$include('%USERPROFILE%/My Documents/js/module3.js');
モジュールが特定のディレクトリにまとめて置かれている場合は、そのディレクトリを相対パスの基準とすることもできる。これは前述の構文において、第二引数以降にそのパスを指定する(環境変数も可)。
($=new ActiveXObject('JScript.Include'))(this, 'D:\\...\\js');
$include('module1.js');
$include('module2.js');
$include('module3.js');
同時にデフォルト文字コードの指定も可能。第二引数以降は順不同および省略可能である。
($=new ActiveXObject('JScript.Include'))(this, 'shift_jis', 'D:\\...\\js');
($=new ActiveXObject('JScript.Include'))(this, 'D:\\...\\js', 'shift_jis');


戻り値とエラー
$include 関数はモジュールが存在しない場合であってもエラーを発生させない。ただし戻り値に false が返る(成功時は true)。


$include の実装について
大まかな流れとしてはコンポーネント側でスクリプト側のグローバル オブジェクトの eval を呼び出すメソッドを用意し、それをスクリプト側の $include 変数に代入しているだけである。本来 eval は呼び出し元コンテキストのスコープチェーンを辿るが、スクリプト側とコンポーネント側ではスコープ チェーンが独立しているため、eval に適用されるコンテキストはコンポーネント側の関数コンテキストではなく、スクリプト側で $include を呼び出したコンテキストとなる。つまり $include 関数は動的スコープになっているのである。本来できないと思われていた eval の処理を関数化できたのはそのためである。


JSCript > Include コンポーネント