(for Internet Explorer)
← ▼ ▲
↑
Option Explicit 変数宣言不要論
変数の宣言が必要なプログラミング言語の場合、変数の宣言をし忘れて書き直すことが
よくあり、コストになっています。 さらに、任意型の宣言は、意味がよく分からないため、
可読性を下げます。 逆に言えば、変数宣言が不要であれば、可読性を上げるのです。
このためか、新しいプログラミング言語 Python などは、ローカル変数の宣言を無くしています。
Option Explicit の無い VBScript では、
・変数宣言が不要になります
・グローバル変数やメンバー変数に定義されていない名前を、関数の中で使った場合、
期待通り、ローカル変数になります
・グローバル変数やメンバー変数に定義してある名前を、関数の中で使った場合、
ローカル変数のつもりで使ったものは、実はグローバル変数なので、期待した動作に
なりません。（対処法は、上記、変数名が衝突する問題を参照）
次の点は、Option Explicit があってもなくても変わりません。
・関数から呼び出し元に戻ったら、ローカル変数は削除されます。
・引数は、ローカル変数になります。
・ 呼び出し元のローカル変数の名前と衝突しても、期待通り、別のメモリの変数になります。
（ちなみに、bash の場合は、同じメモリになる）
変数名が衝突する問題
変数宣言を不要とした場合、変数名が衝突していないことを裏付けることが、Dim
文を実行するときから、テストに合格するときまで遅れます。 これにより、衝突して
いる問題を解決することが、少しだけ難しくなります。
→ Option Explicit 変数宣言について (vbslib)
参考
→ 変数宣言を強制しないといけないのか。 (Web)
参考
(cache)
→ T_NoOptionExplicit フォルダ
テスト
エラー・メッセージの違い
<ERROR err_number='500' err_description='この変数は宣言されていません。'/>
Option Explicit があるとき
<ERROR err_number='13' err_description='型が一致しません。'/>
Option Explicit がないとき
関数として定義されていないシンボルを使って関数呼び出しをしようとすると、
Option Explicit があるときと、無いときで、エラーメッセージが異なります。
VBScript の Dim は、変数名の衝突を早期に検出すること以外に機能はありません。
プログラミング言語によっては変数の型を決めることがありますが、VBScript は、
変数にどの型の値でも入るため変数の型を決める機能はありません。
衝突を検出するだけの命令文は、プログラムが何かをする命令文ではありません。
これは問題です。 プログラムが何をしようとしているのかを理解しようとするときに、
実は何もしていないモノに遭遇しても、何をしようとしているのか懸命に探します。
この脳の状態で、実は何もしていない、ということに気づくのは大変なことです。
大きなコストになります。 また、何もしていないことを読まされることは不快です。
Dim 命令の説明でよくあるのが、使っている変数を挙げているとありますが、別に
Dim 命令が無くても使っている変数は分かります。 変数を初期化している文で、
よく理解できます。 Dim はくどいのです。
なぜ、型が一致しませんと表示されるかというと、定義されていない関数と同じ
名前の新しい変数が自動的に生成され、初期値である Empty が入り、Empty
を使って関数呼び出しをしようと WSH が解釈してしまうからです。
下記のホームページでは、Dim の必要性を説明していますが、必要の根拠となる機能
が無くても、すぐに別のエラーで知ることが出来るため、問題として挙げられていること
が真の問題の解決を困難にすることはありません。
VBScript で、Option Explicit を使った場合、よく遭遇するのは、変数を宣言していない
というエラーです。 このエラーを対処するには、デバッガーを使う形で最初からやり直して、
衝突している変数を知って、Dim 宣言を追加して、また最初からやり直す必要があります。
つまり、２回も再起動が必要になります。 これは結構なコストです。 もし、デバッガー
が無ければ、対処は更に困難になります。
Option Explicit を使っている人は、このコストの大きさに気づいていません。
（私も気づくのに時間がかかりました）
Option Explicit があるときとないときの違い
その他の問題
ローカル変数同士が衝突した場合、原因は、その関数の中だけに絞られます。
つまり、呼び出した先の関数に原因がある一般的なミスに比べると、原因がある範囲が
限定的なので、対処が比較的簡単です。
Sub MyTest()
name = "MyName"
name = "YourName"
Assert FuncA( name ) = "Taro"
End Sub
どれだけ難しくなるかを、見てみましょう。
これは、name = "MyName" であることを期待しているテスト・スクリプトで、name が
"YourName" に変わったためにテストに失敗しています。 この原因を追究するときは、
name の値をどこで代入しているかを調べることになります。 name を上方向に検索して、
name = "YourName" が見つかり、それを対処することになります。
実は、これは、変数が衝突したときよりも発生頻度が高い、一般的なミスによって name
の値が違っているケースと同じ対処法です。 確かに Dim 文によって衝突を検出した
方が速く原因が特定できますが、検出しなくても一般的な対処法で済むわけです。
変数名が衝突するミスは、Dim 文の記述を忘れるミスに比べると、かなり発生頻度が
低いです。 衝突する頻度が低い理由は、意味が異なる変数には、通常異なる名前を
付けるからです。 Dim 文の記述を忘れることが多い理由かは、Dim が何かをする
命令文ではないからです。 何かをさせるために記述しているときに、させることが全て
記述できたら、完成と思うからです。
以上から、発生頻度と修正コストを総合して考えると、Option Explicit をつけた方が
コストが高くなるというわけです。
ローカル変数のつもりで使った変数が、実はグローバル変数やメンバー変数だったとき、
つまり、グローバル変数やメンバー変数がローカル変数に衝突した場合は、ローカル
変数のつもりでデバッグを行おうとするため、対処が困難でしょう。 しかし、グローバル
変数の名前の先頭に "g_" を付けるコーディング・ルールなどを適用すれば、その問題は
無くなります。 Python では、「global」 命令によって問題を回避しています。
グローバル変数同士やメンバー変数同士の衝突は、一般的なバグの対処と変わりません。
また、Option Explicit を使っても、VBS ファイルをモジュール別に分けていると、Dim を
使っても衝突を検出できません。
後でグローバル変数や関数が追加されて、ローカル変数と衝突した場合
後でグローバル変数や関数が追加されて、ローカル変数と衝突した場合は、
ローカル変数に対して Dim 宣言すれば問題に対処できます。
← ▼ ▲
名前空間の汚染の対策
グローバル変数を、１つのオブジェクトにまとめると、アプリケーションや組み合わせる
ライブラリが使えるグローバル変数名に制限が少なくなります。 言いかえれば、名前空間を
汚染することが最小限の抑えることができます。
Sub SampleFunction()
Set g = g_VBScriptLibraryX
g.DoFunction
g.Variable = 1
End Sub
関数の定義は、クラスの中のメソッドに定義します。
Dim g_VBScriptLibraryX
Set g_VBScriptLibraryX = new VBScriptLibraryX_Class
Class VBScriptLibraryX_Class
Public LibraryFunction
Public LibraryVariable
Private Sub Class_Initialize()
Set g = Me
Set g.LibraryFunction = GetRef( "LibraryFunction" )
End Sub
End Class
Sub LibraryFunction()
'// ...
End Sub
すでにある関数の定義から、名前空間の汚染に対策するまでの移行期間では、
次のように、関数の参照をメンバー変数に代入します。
（なお、VBScript では、匿名関数を定義できません。）
Dim g_VBScriptLibraryX
Set g_VBScriptLibraryX = new VBScriptLibraryX_Class
Class VBScriptLibraryX_Class
Public Sub LibraryFunction()
'// ...
End Sub
End Class
VBScript では、クラスをクラスの中に入れることができません。
オブジェクトを生成するメソッド（new_<クラス名>）を用意し、実際のクラス名の先頭に、固有
のライブラリの名前を付けるようにします。
Class VBScriptLibraryX_Class
Public new_SampleClass
Private Sub Class_Initialize()
Set g = Me
Set g.new_SampleClass = GetRef( "new_VBScriptLibraryX_SampleClass" )
End Sub
End Class
Class VBScriptLibraryX_SampleClass
'// ...
End Class
Function new_VBScriptLibraryX_SampleClass()
Set new_VBScriptLibraryX_SampleClass = new VBScriptLibraryX_SampleClass
End Function
Sub SampleFunction()
Set g = g_VBScriptLibraryX
Set object = g.new_VBScriptLibraryX_SampleClass()
End Sub
オブジェクトを生成するときは、次のようになります。
未確認
変数
関数
クラス
この問題の対策は、ライブラリ固有の長めの名前のグローバル変数を使い、ライブラリを
使うときは、１文字の変数に代入してから使います。 なお、すべての関数の先頭に、この
代入が必要になります。
Dim g_VBScriptLibraryX
Set g_VBScriptLibraryX = new VBScriptLibraryX_Class
Class VBScriptLibraryX_Class
Public LibraryVariable
Private Sub Class_Initialize()
LibraryVariable = 1
End Sub
End Class
→ 変数
→ 関数
→ クラス
変数は、メンバー変数にします。
メンバー変数の拡張
VBScript のクラスは、動的にメンバー変数を追加することができません。
このため、ライブラリが、複数の .vbs ファイルに分かれていると、関数などを１つの
クラスのメソッドにまとめることができません。
１つのファイルに集めるスクリプトを動かしてから、ライブラリを動かす（関数定義する）
とよいでしょう。
→ メンバー変数の拡張
→ get_ModuleConsts : クラスの定数 (vbsool)
参考
すでにあるグローバル変数の定義から、名前空間の汚染に対策するまでの移行期間では、
次のように、プロパティを使います。
Dim g_VBScriptLibraryX
Set g_VBScriptLibraryX = new VBScriptLibraryX_Class
Class VBScriptLibraryX_Class
'//[Variable]
Public Property Let Variable( x ) : g_Variable = x : End Property
Public Property Get Variable() : Variable = g_Variable : End Property
End Class
Dim g_Variable
← ▼ ▲
配列、Array、ReDim Preserve
→ Dim ステートメント (Web)
→ ReDim ステートメント (Web)
Dim a(), b
ReDim a(1), b(1)
a(1) = "abc"
WScript.Echo "1=" & a(1) ' 1=abc
ReDim Preserve a(2)
WScript.Echo "0=" & a(0) ' 0=
WScript.Echo "1=" & a(1) ' 1=abc
WScript.Echo "2=" & a(2) ' 2=
'WScript.Echo "3=" & a(3) ' エラー
Array_echo( a )
Sub Array_echo( arr )
Dim i
WScript.Echo "UBound="&UBound( arr )
For Each i In arr
WScript.Echo "each = " + i
Next
End Sub
ReDim Preserve は、要素の内容を
保持したまま、要素の数を変える。
ReDim するなら Dim は不要。 () も不要。
ReDim a(1) で、a(0)〜a(1) が使える
For Each が使える
1=abc
0=
1=abc
2=
UBound=2
each =
each = abc
each =
出力
配列は引数に渡せる
Dim a() とすると a は配列になりますが、配列に固定されてしまいます。
Dim b : ReDim b(1) としても b は配列になり、後で b に配列以外を格納することもできます。
Redim Preserve a(-1) もできますが、Excel では a = Array() とする必要があります。
二次元配列は Redim a( 2, 3 ) のようにコンマで区切ります。
{ 1, 2, 3 } のような初期化子はありませんが、a = Array( 1, 2, 3 ) はできます。
空要素を指定したり、階層化することもできます。
配列の最後に追加する
ReDim Preserve arr( UBound( arr ) + 1 )
arr( UBound( arr ) ) = 1234
配列のメンバ変数に対する Redim は、メンバ関数の中でしかできません。
返り値を ReDim で配列にすることはできませんが、配列変数を返り値に代入できます。
引数に渡した配列は、関数内で ReDim して要素数を変えることができます。
→ オブジェクトの配列を生成する new_ClassA_array
→ Deep Decryption - VBScript Tips - 配列再考 (Web)
配列全体のコピー
Dim a, b
a = Array( 1, 2 )
b = a
b = a のように代入すると、すべての配列の要素をコピーします。
C 言語のような参照ではありません。
Dim b() と宣言すると、コピーできなくなります。
関数の返り値に配列を渡すこともできます。
Dim a
a = Array( 1, 2, 3 )
a = Array( )
a = Array( 1 )
a = Array( 1, "ABC" )
a = Array( , "ABC" )
FuncA Array( _
Array( 1, "ABC" ),_
Array( 2, "DEF" ) )
arr
arr
arr
arr
1234
← Array 関数は、配列を返します。
a(0)=1, a(1)=2, a(2)=3, UBound(a)=要素数-1
← 空の配列, UBound(a) = -1
← 要素数が１つの配列
← 数値と文字列が混在した配列
← 空要素を含む配列。 VarType( a(0) ) = vbError
← 配列が入っている配列を関数 FuncA に渡す
（階層化配列）
Array 関数
Dim と ReDim
→ 配列全体のコピー
→ 配列の最後に追加する
→ Array 関数
→ Dim と ReDim
→ 配列かどうかの判定 IsArray
配列のメンバ変数に対する Redim は、メンバ関数の中でしかできません。
オブジェクトのメンバを参照するピリオド演算子を使った時点で、Redim した内容は
メンバ変数には反映されません。
ArrayClass(vbslib) を使えば、クラスの外から配列の要素数を変えることができます。
上記のように、１要素ずつ増やすと、処理速度はかなり遅くなります。
→ 引数に配列を渡す速度
→ 配列要素を追加する速度
← ▼ ▲
Split
Dim a, e
a = Split( "abc, 123 ,""ABC""", "," )
For Each e In a : echo "["+ e +"]" : Next
Function Split( Str as stirng, Term as string ) as Array of string
文字列を、指定の文字で区切って、配列に格納します。
【引数】
Str
Term
分割しようとする文字列
区切り文字
返り値
分割した文字列の配列
[ 親: vbs の配列 ]
a(0) = [abc]
a(1) = [ 123 ]
a(2) = ["ABC"]
(Web)
→ MeltCSV
関連
サンプル
Split( "a,b," ) '// Array( "a", "b", "" )
→ Join
→ get_PathNameRegularExpression
← ▼ ▲
Byte 配列
Read (ADODB.Stream)
すると、Byte 型の配列が返ります。
バイナリ・ストリームから
VBScript には、バイナリ（Byte 型配列）を作ることはできませんが、ADODB を使うと作れます。
Write (ADODB.Stream)
バイナリ・データの一部をリードしたByte 配列は、そのまま
することが
Dim binary '// as Byte()
Dim strbin '// as String
Dim value '// as Integer
value = AscB( binary ) '// 先頭バイトの値
strbin = MidB( binary, 2, 1 ) '// 2バイト目
value = AscB( strbin )
bin = binary(1) '// ( ) を使うとエラー
Dim rf, wf '// as ADODB.Stream
rf.Position = &h10
binary = rf.Read( &h28 )
wf.Write binary '// rf のオフセット &h10 から %h28バイトを wf へ
バイナリの内容を確認するには、AscB と MidB を使います。 AscB 関数だけで、バイナリの
数値を取得できますが、AscB 関数は先頭の文字（バイト）しか数値に変換しないので、多くの
場合で MidB も使うことになります。 ちなみに、MidB の代わりに Byte 配列の配列番号を
使って参照しようとするとエラーになります。
ただし、MidB などの文字列操作関数や + 演算子で返される文字列は、Byte配列ではなく
String 型になるため、 ADODB.Stream の Write に渡すことができません。
AscB や MidB が使えるのは、VBScript が Byte 配列を String 型（文字列）に暗黙的に変換
しているからです。 ただし、VBScript の通常の文字列は、Unicode（2バイト文字）なので、
通常、バイト単位の文字として扱う末尾にBが付いた関数を使います。
ChrB, LeftB, RightB, LenB, StrComp 関数や、+ 演算子などが使えます。
Dim rf, wf '// as ADODB.Stream
binary = rf.Read( adRealAll )
strbin = MidB( binary, &h10, &h28 )
wf.Write strbin '// String 型なのでエラー
できます。 つまり、バイナリ・データの一部を抽出することができます。
strbin = LeftB( binary, 3 ) + ChrB( &h0A ) + RightB( binary, 3 )
バイナリの内容を確認する
バイナリの内容を加工する （文字列としての Byte 配列）
→ バイナリの内容を確認する
→ バイナリの内容を加工する （文字列としての Byte 配列）
→ Byte 配列の制限
ReDim Preverve binary（10)
次のコードはエラーになります。
binary(0)
Byte 配列は、配列を使う関数のうち一部の関数しか使えません。 次の関数は使えます。
Size = UBound( binary ) + 1
LBound( binary ) '// 常に 0
Byte 配列の制限
Byte 配列は、( ) を使って配列の要素にアクセスできない代わりに、String 型に暗黙変換
することができるので、文字列を扱う関数を使うことができます。（後述）
VarType( binary ) = vbArray + vbByte = &h2011
つまり、VBScript では、文字列関数や配列機能を使ってバイナリを編集することはできません。
編集するには、ADODB.Stream を活用することで、間接的に編集することができます。
なお、以下に説明する内容は、
まず、バイナリのすべての値 0x00〜0xFF を Unicode を使ってストリームに作成します。
その後、必要に応じて、バイナリを格納する別のストリームに
Dim i, f
Set f = CreateObject( "ADODB.Stream" )
f.Charset = "unicode"
f.Open
'// FE FF 00 01 02 ... FD FE FF
For i=0 To 255 Step 2 : f.WriteText ChrW( i + (i+1)*&h100 ) : Next
f.Position = 0
f.Type = Me.adTypeBinary
f.Position = Values + 2
f2.Position = ...
f.CopyTo f2, 1
f.Close
します。
CopyTo (ADODB.Stream)
の内部で使われています。
BinaryArray (vbslib)
バイナリの内容を比較する
→ バイナリの内容を比較する
Dim f1, f2 '// as ADODB.Stream
bin1 = f1.Read( adRealAll )
bin2 = f2.Read( adRealAll )
If StrComp( bin1, bin2, 0 ) = 0 Then echo "same"
Byte 配列を StrComp に渡すことで、バイナリ比較ができます。
文字列のように大小比較もできます。
参考
→ ReadStruct (BinaryArray) (vbslib)
サイズ 0 のバイト配列
バイナリ・ストリームから Read (ADODB.Stream) すると、Byte 型の配列が返りますが、
サイズ = 0 で Read すると、Null が返ります。 サイズ 0 のバイト配列は取得できません。
そして、Null を Write すると、エラーになります。
→ new_BinaryArray と GetEchoStr によるダンプ (vbslib)
← ▼ ▲
単体と配列の共用変数
VBScipt は、１つの変数に一般的な変数と配列のどちらでも入れることができます。
これを応用して、１つの値の指定と、複数の値の指定を、１つの変数で対応できます。
Sub main()
Dim a
a = 1 : FuncA a
a = Array( 1, 2, 3 ) : FuncA a
a = Empty : FuncA a
End Sub
下記のように、関数の実際の処理を行う前に配列に統一すると、以降の処理で場合分け
しなくて済みます。
FuncA_sub をわざわざ作っている理由は、下記のように配列を ByVal 引数にすると、
コピーが発生して遅くなってしまうのを避けるためです。
Sub FuncA( ByVal Param ) '// コピーが発生
If not IsArray( Param ) Then Param = Array( Param )
Dim elem
For Each elem In Param
WScript.Echo elem
Next
End Sub
→ ByValArrBench.zip
ByVal 配列のコピーにかかる時間の計測
Sub FuncA( Param )
If IsArray( Param ) Then
FuncA_sub Param
ElseIf IsEmpty( Param ) Then
FuncA_sub Array( )
Else
FuncA_sub Array( Param )
End If
End Sub
Sub FuncA_sub( Param ) '// Param は必ず配列
Dim elem
For Each elem In Param
WScript.Echo elem
Next
End Sub
関数の引数に、単体と配列のどちらでも指定できるようにする
プロパティに、単体と配列のどちらでも格納できるようにする (ToStandard)
プロパティに、一般変数と配列変数を設定できるとき、処理を行う前に次のメソッドを
呼び出すと、すべて配列として処理を記述できます。
Public Sub ToStandard()
If IsEmpty( Me.m_PropA ) Then
Me.m_PropA = Array( )
ElseIf not IsArray( Me.m_PropA ) Then
Me.m_PropA = Array( Me.m_PropA )
End If
End Sub
（メモ） Property Set で実現する方法も考えられますが、配列のコピーが入ってしまいます。
Private Sub Class_Initialize()
Me.m_PropA = Array( )
End Sub
Public Property Set PropA( Value )
If IsEmpty( Value ) Then
Me.m_PropA = Array( )
ElseIf not IsArray( Value ) Then
Me.m_PropA = Array( Value )
Else
Me.m_PropA = Value
End If
End Sub
→ 関数の引数に、単体と配列のどちらでも受け取れるようにする
→ プロパティに、単体と配列のどちらでも格納できるようにする
← ▼ ▲
(Web)
If file Is Nothing Then
If IsNull( a ) Then
(Web)
If IsNumeric( a ) Then
If VarType( a ) = vbInteger Then
vbString(=8)
Nothing
Empty
If IsEmpty( file ) Then
Null
vbEmpty(=0)
VarType
-
文字列
(Web)
Function TypeName( s as variant ) as string
型の判定 (TypeName, VarType)
判定文の例
Function VarType( s as variant ) as integer
TypeName
参考
"Empty"
"Nothing"
"Null"
"String"
Class Foo
"Foo"
If IsObject( a ) Then
If TypeName( a ) = "Foo" Then
"Integer"
整数
浮動小数
（倍精度）
"Double"
型
定数(例)
Empty
Nothing
Null
"ABC"
123
1.2
If VarType( a ) = vbString Then
配列
"Variant()"
8024
If IsArray( a ) Then
vbDate(=7)
日付
CDate("7/4")
#7/4#
"Date"
If IsDate( a ) Then
new Foo
Array(1,2)
1
vbInteger(=2)
vbDouble(=5)
vbObject(=9)
論理
True,False
vbBoolean(=11)
"Boolean"
If IsNumeric( a ) Then
If VarType( a ) = vbDouble Then
a=Array(,1)
の a(0)
配列の
空要素
"Error"
vbError(=10)
If VarType( a(i) ) = vbError Then
浮動小数
（倍精度）
"Single"
vbSingle(=4)
If IsNumeric( a ) Then
If VarType( a ) = vbSingle Then
関数
Sub/Function
"Object"
vbObject(=9)
If IsObject( a ) Then
If VarType( a ) = vbBoolean Then
← すべての型の一覧
"Byte"
vbByte(=17)
"Long"
vbLong(=3)
0〜255
-32768〜32767, &h0000〜&hFFFF
-2147483648〜2147483647
&h10000〜&hFFFFFFFF
IsDate
←
↓