月曜日, 11月 20, 2006

VBScript 外部ファイルのインクルード方法

VBScript †

外部ファイルのインクルード方法 †
Execute ステートメントを使うことで外部ファイルを取り込むことが可能。

サンプル †
Include.vbs
Const g_strA = "AAA"
Const g_strB = "BBB"ExecuteTest.vbs
Option Explicit
Main

Sub Main
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")

Dim f
Set f = fso.OpenTextFile( "d:\home\edu\VBScript\ExecuteTest\include.vbs", 1, False )
Dim strBuf
strBuf = f.ReadAll
WScript.Echo "<内容確認>"
WScript.Echo strBuf
Execute strBuf
WScript.Echo "<Execute の実行結果>"
WScript.Echo g_strA
WScript.Echo g_strB
End Sub実行結果
<内容確認>
Const g_strA = "AAA"
Const g_strB = "BBB"

<Execute の実行結果>
AAA
BBB

Windows Script Host の環境から実行時にロードする方式

[[$$toc]]
----
* はじめに

Lisp などではファイルを実行時にロードして解釈実行することが可能です。例えば Scheme の実装である Gauche では、[http://www.shiro.dreamhost.com/scheme/gauche/man/gauche-refj_106.html#SEC110 6.19.1 Schemeファイルのロード] で説明されています。この実行時にロードする枠組みが用意されていると何がうれしいかというと、ライブラリのようにいつも利用するスクリプトを外出しにすることができます。また実行時に読み込むプログラムをかえ動作をかえたいようなことも実現可能です。

ここでは、Windows Script Host の環境から実行時にロードする方式をまとめます。

* 方式

現在 Windows Script Host の環境から三つの方式で動的にファイルをロードすることが可能です。

** Windows Script File フォーマットを利用

Windows Script File (.wsf) フォーマットで記述されたファイルは、記述言語を複数選択できたり job という単位でスクリプトをグルーピングできたりいくつか拡張されています。このうち script 要素に src プロパティーを使って外部ファイルをロードすることが出来ます。

** Windows Script Component を利用

Windows Script Component (.wsc) として記述されたファイルは、ActiveX オブジェクトとして任意の ActiveX クライアントから使用できます。

** eval() グローバルメソッドを利用

スクリプトのトップレベル、つまり一番外のスコープで eval() グローバル
メソッドを実行すると、それはそこに書き出されているものを実行している
のと同じ効果が得られます。この特徴を利用してファイルの中身をロードして eval() に渡すという方式です。

* 関連ログ

WSH Lab. 掲示板への投稿を記録目的で残しておきます。

<<<
-- 別ファイルに記述された関数を呼ぶために

(2004/05/08 00:43:29 JST) 一部加筆修正を行った。((a-2) の追加。ただ (a) と記述していたところを (a-1) と訂正)
{{{
--------------------------------------------------------------------------------
いりや さん 2004年 01月 24日 18時 26分 14秒


owl さん、

> 余談ですが、WScript.ShellのRunメソッドはショートネームじゃないと
> ダメなんですね。

いえ、ロングネームでも大丈夫です。

ではなぜ owl さんの例で失敗するか、それは Windows Shell が引数のパース
処理に失敗しているからです。

パース処理において引数の区切り子 (Separator) の存在はご存知だと思います。
今回のケースは区切り子を含む引数を Windows Shell に与えるにはどうしたら
よいか? に該当します。こうした場合は区切り子をエスケープする指示を与え
てやらねばなりません。今回の場合は引数の両端をダブルクォーテーションで
括ってやりさえすればうまくいくでしょう。

一般的なエスケープのやるやり方については msdn を照会いただくとよいです。

--------------------------------------------------------------------------------
owl さん 2004年 01月 24日 18時 12分 32秒


To いりやさん、
Cc つちやさん、

とても親切な回答ありがとうございます。
Webに関して知識が乏しいため、wsfの意味が解っていませんでした。

> owl さんの投函を拝見すると、同一のプログラム言語で再利用性の
> 高いものをまとめて別ファイルに外だしすることを狙っているよう
> に見受けられました。実行時に動的に読み込むファイルがかわるケー
> スというのは少ないのではないでしょうか。であれば (a) が適当
> かとおもいます。

ご明察の通りです。

(a)の方法で、以下のようにSample1.vbs → Sample1.wsfとする
とうまくいきました。

ありがとうございました。

関数SampleをCallしてもDim args以下も実行されしまうんですね。
「インデックスが有効範囲にありません。」と怒られてしまいました。

*** Sample1.wsf ***************************

<script language="VBScript" src="Sample2.vbs">
Call Sample("外部引数")
</script>

*******************************************

> *** Sample2.vbs ***************************
> Dim args
> Set args = WScript.Arguments
> (以下、省略)
>
> Sub Sample(arg)
> MsgBox arg
> End Sub
> *******************************************

余談ですが、WScript.ShellのRunメソッドはショートネームじゃないと
ダメなんですね。

sh.Run "C:\Documents and Settings\owl\デスクトップ\Sample2.vbs"

sh.Run "C:\Docume~1\owl\デスクトップ\Sample2.vbs"

--------------------------------------------------------------------------------
いりや さん 2004年 01月 24日 16時 35分 26秒


owl さん、つちやさん、

| もっとダイレクトに呼び出す事はできないものでしょうか?

整理しながらちょっと考えてみましょう。

別ファイルに書かれているものを、誰が取り込んでどう実行させる
か、という点でいくつか方式が考えられます。

AYA さんの解説は、(a-1) で、つちやさんの例は (b-1), (b-2) 、
owl さんの例は (c-1) に対応します。

「ダイレクトに」という言葉のニュアンスをきちんと理解している
か不安ですが、「あたかも同ファイルに書き込まれているが如く呼
び出したい」、というお話であれば (a-1) を方式として採用すれ
ばよいとおもいます。

ただし (a-1) には制限があります。

(a-1) は、Windows Script Host をスクリプトホストとして使えない
場合には適用できません。また (a-1) において、ファイル名は .wsf
ファイルに静的に記述します。これは実行時に変更できないことを
意味します。実行時にファイル名を変更してそのファイルを読み込
みたい、のであれば (b-1) の採用を検討すればよいでしょう。

(b-2) は、Component Object Model ですから、切り口をもつ任意
のプログラム言語から使用できます。例えば前回の投函で紹介した
UWSC はそういうプログラム言語の一つです。そのように使われる
ことを狙っているのであれば (b-2) を採用すればよいでしょう。

owl さんの投函を拝見すると、同一のプログラム言語で再利用性の
高いものをまとめて別ファイルに外だしすることを狙っているよう
に見受けられました。実行時に動的に読み込むファイルがかわるケー
スというのは少ないのではないでしょうか。であれば (a-1) が適当
かとおもいます。


(a) スクリプトホストが取り込むことに責任をもつ

(a-1) Windows Script Host (wscript.exe/cscript.exe) の .wsf
   ファイルの「インクルードステートメント」機能を利用する

   関数の読み出し方は通常と全くかわらず、あたかも同ファイ
   ルに書き込まれているが如く呼び出す。

   [ 参考 ]
   msdn, Windows スクリプト ファイル (.wsf) を使用する
   http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/script56/html/wsAdvantagesOfWs.asp

(a-2) Internet Explorer の SCRIPT タグを利用する

(b) プログラムが取り込むことに責任をもつ

(b-1) 文字列データをプログラムと見なして実行させる

   ファイルの内容をすべて読み出して、
       VBScript の execute() 関数
       JScript の eval() 関数
   などを利用して文字列データをあたかもプログラムと見做し
   て実行させる。

(b-2) Component Object Model の切り口を利用

   Windows Script Component を VBScript/JScript 言語など
   で作成して、
       VBScript の CREATEOBJECT() 関数
       JScript の new ActiveXObject() 関数
   などを利用して当該オブジェクトのメソッド呼び出しとして
   実行させる。

   [ 参考 ]
   msdn, スクリプト コンポーネント チュートリアル
   http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/script56/html/lettitle.asp

(b-3) スクリプト・コントロール (つちやさんご紹介)

   ・・・よく知りません (^^; ・・・

(c) 取り込むかわりにプログラムを別プロセスとして実行

(c-1) プログラムの引数を関数の引数にみたてて実行させる

   WScript.Shell>>run()、WScript.Shell>>exec() メソッドな
   どで VBScript/JScript のプログラムを別プロセスとして実
   行させる。その際にコマンドラインの引数を設定する。呼び
   出すプログラム側において、引数を受け取って関数の引数に
   渡して実行させる。
}}}

-- cpp の #include を模倣

(2004/05/08 00:43:29 JST) 一部記述誤りを訂正した。
{{{
--------------------------------------------------------------------------------
いりや さん 2003年 10月 12日 15時 04分 01秒


#include のように別ファイルを取り込む方法

昨晩ふっと思いついた方法です。WSH/JScript で有効です。(既出でしたらご
めんなさい)


[ 方式 ]

スクリプトのトップレベル、つまり一番外のスコープで eval() グローバル
メソッドを実行すると、それはそこに書き出されているものを実行している
のと同じ効果が得られる。

この特徴を利用します。


[ 制約 ]

eval() を実行する場所をトップレベル以外、たとえば関数定義部の中にする
と、その関数定義の中でしか利用できません。これは eval() の仕様が、

``The code passed to the eval method is executed in the same context
as the call to the eval method.'' (msdn eval() メソッドの解説より引用)

だからです。

関数を呼び出すときに用意された実行コンテクストは関数を抜けたときに捨
てられます。eval() を通じて実行コンテクストに登録された束縛対 (名前と
関数の本体など) は運命を実行コンテクストとともにします。そのため、関
数を抜けると使用できなくなります。

トップレベルはスクリプトの実行が完了するまでずっと存在していますので、
そこで eval() メソッドを実行するわけです。


[ 方法 ]

トップレベルで、

eval(contentsOfFile(<ファイルパス>);

と書きます。それ以降、<ファイルパス> で定義された関数をあたかも定義済
みがごとく呼び出し可能です。

<ファイルパス> は、例えば '.\\stdio.js' のように設定します。

雰囲気は、C 言語の

#include

っぽいでしょ。:-)


[ 実行例 ]

・main.js (後述する contentsOfFile() などは省略しています)

eval(contentsOfFile('.\\mylib.js'));

WScript.echo(globalVar1);
WScript.echo(globalVar2);
func1();
func2();

・mylib.js

var globalVar1 = 'mylib.js';
var globalVar2 = 2;

function func1() {
    WScript.echo('func1 is defined by mylib.js.');
}

function func2() {
    WScript.echo('func2 is defined by mylib.js.');
}

・実行結果 (出力結果をまとめられるので cscript で実行)

D:\Program Files\uwsc\scripts\load stuff>cscript //nologo main.js
mylib.js
2
func1 is defined by mylib.js.
func2 is defined by mylib.js.

D:\Program Files\uwsc\scripts\load stuff>


[ ソースコード ]

※あらかじめ全角スペースを半角スペースに変換して取り除いておいてくだ
さい。

function contentsOfFile(scriptfile) {
    var readStream = file→readStream(filepath→file(scriptfile));
    var returnValue = '';
    try {
        returnValue = readStream.readAll();
    } finally {
        readStream.close();
    }
    return returnValue;
}

function filepath→file(filepath) {
    var aFileSystemObject = new ActiveXObject('Scripting.FileSystemObject');
    if (!aFileSystemObject.fileExists(filepath))
        aFileSystemObject.createTextFile(filepath, true); // 2nd arg : overwrite
    return aFileSystemObject.getFile(filepath);
}

function file→readStream(file) {
    var ForReading = 1;
    var TristateUseDefault = -2;
    return file.openAsTextStream(ForReading, TristateUseDefault);
}
}}}

>>>