XP SP3 で WSH が 5.7 になりました。
とは言え、何がどう変わったかのか分かりませんね。障害もそのままだし。:-(
« 2008年4月 | トップページ | 2008年6月 »
とは言え、何がどう変わったかのか分かりませんね。障害もそのままだし。:-(
Shell(Explorer) の場合には、
Set ie=CreateObject("InternetExplorer.Application")
を
Set ie=GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}")
に変えます。
この new:{clsid} モニカは、Windows 2000 や Windows XP などで使えるようです。
他の対処法としては、WSF にして、
<object id="ie" classid="clsid:C08AFD90-F2A1-11D1-8455-00A0C91F3880" />
とするか、レジストリに、
[HKEY_CLASSES_ROOT\Explorer.Application]
[HKEY_CLASSES_ROOT\Explorer.Application\CLSID]
@="{C08AFD90-F2A1-11D1-8455-00A0C91F3880}"
のような ProgID をでっち上げて、
Set ie=CreateObject("Explorer.Application")
で使います。
そのせいか、IE7 で、「お気に入り」や「リンク」からの起動が制限されました。
When you try to open a shortcut in Internet Explorer 7, the target executable file cannot be loaded
IE6 までは、「お気に入り」や「リンク」に、スクリプトや HTA を入れて、IE から起動できましたが、IE7では、ダウンロードのダイアログが出て、更にセキュリティの警告が出て、と大変なことになりました。
で、その対処法ですが、これも難しそうです。
1番目のダウンロードのダイアログは、どこかの設定を変えればよいのかも知れませんが、それには、きっと、セキュリティ上の問題があるのでしょう。
2番目のセキュリティの警告のほうは、署名を付けて、その署名を信頼してやればよいのでしょうが、それも面倒です。
ツールバーの「リンク」の場合は、右クリック、「プログラムから開く(H)」で(大概は、先頭の)アプリを起動します。
「リンク」の場合は、制限されるのは、「開く」だけなので、同じ内容の関連付けを別のキーで作れば、右クリックで起動できます。
エクスプローラバーの「お気に入り」の場合は、打つ手なし。
もし、「お気に入り」の「フォルダショートカット」を「リンク」の中に作れば、ツールバーの「リンク」の延長で「お気に入り」が使えます。
ただし、再帰の無限ループなど、「フォルダショートカット」は、トラブルの元だったような。。。
printキーを潰して、印刷の代わりに起動する手もありますが。。。
なので、フォーカスが移ってよいなら、タスクバーから起動するようにするか、どうしてもフォーカスを変えたくなければ、コンテキストメニュー拡張にするしかないでしょう。
今まで (IE6) は、Shell.ApplicationのWindows().Item() で、現在または最後にアクティブな IE が捕捉できましたが、それができなくなりました。:-(
Shell.ApplicationのWindows().Item() で捕捉できるのは、現在または最後にアクティブな Shell だけです。
それを利用していたスクリプト類は、全滅です。:-(
で、その対処法ですが、難しそうですね。駄目っぽい。
「現に」アクティブな IE の場合には、
Set ie=CreateObject("Shell.Application").Windows().Item()
を
Set Shell=CreateObject("Shell.Application")
For Each ie In Shell.Windows()
If TypeName(ie.Document)="HTMLDocument" Then If ie.Document.hasFocus() Then Exit For
Next
に変えます。
ただし、フォーカスを Web ページ部分に置いておく必要があります。
もし、フォーカスが外れてると、見つけ損ないます。
HTA スクリプトは、フォーカスを奪うので、この手は使えません。
「最後にアクティブ」の部分は、打つ手なし、です。
今まで (IE6) は、これを利用して、ブックマークレットっぽいことが出来ましたが、IE7 ではもう無理みたい。
これからは、コンテキストメニュー拡張でやるしかないかなぁ~。
今まで (IE6) は、IE (IExplore.exe) でフォルダを開いたり、Shell (Explorer.exe) で Web ページを開いたり、相互乗り入れが出来ましたが、それが出来なくなりました。:-(
KB928675 Windows シェルから Internet Explorer 7 の分離
それを利用していたスクリプト類は、全滅です。:-(
で、その対処法ですが、Shell(Explorer)の場合には、
Set ie=CreateObject("InternetExplorer.Application")
を
Set ie=GetObject("new:{C08AFD90-F2A1-11D1-8455-00A0C91F3880}")
に変えます。
VBAなどでは、
C:\WINDOWS\system32\shdocvw.dll
Microsoft Internet Controls
を参照設定して、
Dim ie As SHDocVw.ShellBrowserWindow
Set ie = New SHDocVw.ShellBrowserWindow
を使います。
これは、まだ、対処法があるのでよいけれど。。。
Option Base {0|1}にも対応します。
Function UpperBound(a)
UpperBound = UBound(Array())
On Error Resume Next
UpperBound = UBound(a)
End Function
または、
Function UpperBound(a, Optional n = 1)
UpperBound = UBound(Array())
On Error Resume Next
UpperBound = UBound(a, n)
End Function
配列の代用として使う場合は、インデックスの抜けを避けて使います。
もし、抜けがある場合は、追加インデックスに a.Keys()(a.Count-1)+1 を使います。
a.Add a.Keys()(a.Count - 1) + 1, f
または、
a.Item(a.Keys()(a.Count - 1) + 1) = f
ただし、これは、a.Count=0 のときに、エラーになるので、
If a.Count Then k = a.Keys()(a.Count - 1) + 1 Else k = 0
LBound()は、VBScriptではグローバルオブジェクトのメソッドなので、
Call LBound(a)
で呼べますが、VBAではVBA関数なので?、Callだとエラーになります。
r = LBound(a)
なら、よいようですが、ダミー変数が必要です。
If LBound(a) Then:
これなら、ダミー変数は不要です。VBA/VBScript共通に使える構文です。
VBScriptやVBAでは、以下のようにLBound()のエラーを拾う。のが普通のやり方みたいです。
Function NumberOfDimensions(a)
NumberOfDimensions = -1
On Error Resume Next
Do
NumberOfDimensions = NumberOfDimensions + 1
If LBound(a, NumberOfDimensions + 1) Then:
Loop Until Err
End Function
ここで、次元数=0が、「配列が初期化されてない。」の意味です。
ところで、JScriptには、そういうメソッドがあります!えっ?
Function NumberOfDimensions(a)
Dim sc As Object
Set sc = CreateObject("ScriptControl")
sc.Language = "JScript"
sc.AddCode "function NumberOfDimensions(a){return new VBArray(a).dimensions();}"
NumberOfDimensions = sc.CodeObject.NumberOfDimensions(a)
End Function
空の配列も、未初期化の配列も、エラーハンドリングも使わないで、なぜか、逆説的に、デクレメントやEraseを使う方法です。
Dim a() As String
Dim f As String
ReDim a(0)
f = Dir("*")
Do While Len(f)
a(UBound(a)) = f
ReDim Preserve a(UBound(a) + 1)
f = Dir()
Loop
If UBound(a) Then
ReDim Preserve a(UBound(a) - 1)
Else
Erase a
End If
MsgBox Join(a, vbLf)
これも、要素がないと、後で使うときにエラーになるので、要素が必ず存在するような場合に使うとよいでしょう。
或いは、配列の最後に空の要素を余分に持つというコンベンションにすれば、デクレメントやEraseは不要です。
For k = 0 To UBound(a) - 1
で回す。とか、
Join(a, vbLf)
で末尾にも改行が付いて、これはこれで便利です。
Variant型の空の配列は、
Dim a As Variant
a = Array()
で作れますが、Variant型以外のデータ型の場合は?
String型は、
Dim a() As String
a = Split("")
Byte型は、
Dim a() As Byte
a = ""
で作れますが、Long型などは、空の配列が作れません。
もし、空の配列の代わりに、未初期化の配列を使うと、UBound(a)がエラーになります。
そこで、空の配列の代わりに、未初期化の配列を使うやり方。
Dim a() As String
Dim n As Long
Dim f As String
f = Dir("*")
Do While Len(f)
n = -1
On Error Resume Next
n = UBound(a)
On Error GoTo 0
ReDim Preserve a(n + 1)
a(UBound(a)) = f
f = Dir()
Loop
MsgBox Join(a, vbLf)
さすがに手順がちょっと多いので、別にPush関数を作ってやるほうがよいでしょう。
Dim a() As String
Dim f As String
f = Dir("*")
Do While Len(f)
Push a, f
f = Dir()
Loop
MsgBox Join(a, vbLf)
Sub Push(Items, Item)
Dim n As Long
n = -1
On Error Resume Next
n = UBound(Items)
On Error GoTo 0
ReDim Preserve Items(n + 1)
Items(UBound(Items)) = Item
End Sub
ただし、未初期化のままだと、後で使うときにエラーになるので、要素が必ず存在するような場合に使うとよいでしょう。
なので、要素がないこともあるときは、空の配列が作れるデータ型なら、それで。
空の配列が作れないデータ型なら、できればVaiant型にしたほうがよいでしょう。
Scripting.Dictionaryを配列の代わりに使います。
Dim a As Object
Dim f As String
Set a = CreateObject("Scripting.Dictionary")
f = Dir("*")
Do While Len(f)
a.Add a.Count, f
'または、好みで、
' a.Item(a.Count) = f
f = Dir()
Loop
MsgBox Join(a.Items(), vbLf)
push()メソッド同様に1行で書けます。
配列の代わりにScripting.Dictionaryを使うときのポイントは、
Dictionaryのキーにインデックス(0~)を使います。
配列でUBound(a)+1と書く代わりにDictionaryでa.Countを使います。
配列でa(k)と書く代わりに、Dictionaryでa.Item(k)と書きます。
Dictionaryがa.Items()で配列になります。
For Each k In aで取り出せるのはキー。a.Item(k)で値。
For Each x In a.Items()で値。
例えば、VBAのDir()関数でファイルを列挙して配列に格納するには、
Dim a As Variant
Dim f As String
a = Array()
f = Dir("*")
Do While Len(f)
ReDim Preserve a(UBound(a) + 1)
a(UBound(a)) = f
f = Dir()
Loop
MsgBox Join(a, vbLf)
JScriptならArrayオブジェクトのpush()メソッドで簡単なのですが、VBScriptやVBAの配列では2行になります。
もし、push()メソッド相当のPush関数を別に作ってやれば、1行になります。
Dim a As Variant
Dim f As String
a = Array()
f = Dir("*")
Do While Len(f)
Push a, f
f = Dir()
Loop
MsgBox Join(a, vbLf)
Sub Push(Items, Item)
ReDim Preserve Items(UBound(Items) + 1)
Items(UBound(Items)) = Item
End Sub
ここでは、空の配列を作るために、Array()を使用しているので、使えるのは、Variant配列だけです。:-(
ラッパ関数を介して、インデックスアクセスするのが、お勧めです。
Set sc=CreateObject("ScriptControl")
sc.Language="JScript"
sc.AddCode "function set(a,k,v){a[k]=v;}function get(a,k){return a[k];}"
参照は、
x=a[k]
の代わりに、
x=sc.CodeObject.get(a,k)
または、
x=sc.Run("get",a,k)
設定は、
a[k]=x
の代わりに、
call sc.CodeObject.set(a,k,x)
または、
call sc.Run("set",a,k,x)
性能は、o(n)なので、n=a.length が大きくても、使えます。
また、Run()よりCodeObjectのほうが速くてよいでしょう。
slice()/splice()を使って、見た目、スマートにインデックスアクセスできます。でも、隠れメタボかも?
参照は、
x=a[k]
の代わりに、
x=a.slice(k,k+1).pop()
設定は、
a[k]=x
の代わりに、
call a.splice(k,1,x)
ただし、性能は、o(n**2)なので、n=a.length が大きいときは使わないほうがよいでしょう。
VBScriptの配列は、a(k)でインデックスアクセスします。
一方、JScriptのArrayオブジェクトは、JScriptで、a[k]でインデックスアクセスします。
では、JScriptのArrayオブジェクトを、VBScriptやVBAで、どうやってインデックスアクセスするか?
既存のインデックスなら、プロパティとして、a.[0]でアクセスできますが。。。
参照は、
x=a[k]
の代わりに、
x=Eval("a.[" & k & "]")
また、VBAなら、
x = CallByName(a, k, VbGet)
設定は、
a[k]=x
の代わりに、
Execute "a.[" & k & "]=x"
しかし、EvalやExecuteは使いにくいですね。新規のインデックスにも使えないし。
取り出し(pop/shift)は遅いので、For Eachで列挙するのが速くてよいでしょう。
Set sc=CreateObject("ScriptControl")
sc.Language="JScript"
set a=sc.Eval("new Array()")
t1=Timer
For k=1 To 1024*512
a.push k
Next
MsgBox Timer-t1
'10.35156
t1=Timer
For Each x In a
Next
MsgBox Timer-t1
'0.3046875
もし、インデクスで取り出したければ、For Eachで配列に転写するか、Evalを使うとよいでしょう。
t1=Timer
redim b(a.length-1)
k=-1
For Each x In a
k=k+1
b(k)=x
Next
MsgBox Timer-t1
'1.210938
t1=Timer
For k=1 To a.Length
x=Eval("a.[" & k-1 & "]")
Next
MsgBox Timer-t1
'13.78906
VBAの場合は、CallByName()も使えます。
t1 = Timer
For k = 1 To CallByName(a, "length", VbGet)
x = CallByName(a, k - 1, VbGet)
Next
Debug.Print Timer - t1
'1.8125
JScriptのArrayオブジェクトから取り出すとき、pop()も遅いが、shift()はもっと遅い。
Set sc = CreateObject("ScriptControl")
sc.Language = "JScript"
Set a = sc.Eval("new Array()")
t1 = Timer
For k = 1 To 1024 * 16
a.push k
Next
MsgBox Timer - t1
'0.28125
t1 = Timer
For k = 1 To a.length
x = a.pop()
Next
MsgBox Timer - t1
'23.76172
t1 = Timer
For k = 1 To a.length
x = a.shift()
Next
MsgBox Timer - t1
'101.3867
pop()も、shift()も、共にo(n**2)みたい。
より遅いshift()は、使わないほうがよいかも。
もし、必要なら、reverse()とpop()で代替したほうがよいでしょう。
JScriptのArrayオブジェクトに追加するとき、push()はそれほどでもないが、unshift()はとても遅い。
t1 = Timer
Set sc = CreateObject("ScriptControl")
sc.Language = "JScript"
Set a = sc.Eval("new Array()")
For k = 1 To 1024 * 512
a.push k
Next
MsgBox Timer - t1
'9.523438
push()は、VBScriptやVBAの配列をReDimで漸増するより速いので、その代替に使えます。
t1 = Timer
Set sc = CreateObject("ScriptControl")
sc.Language = "JScript"
Set a = sc.Eval("new Array()")
For k = 1 To 1024 * 8
a.unshift k
Next
MsgBox Timer - t1
'5.617188
unshift()は、怖ろしく遅いので使うべきでないかも。
push()は、o(n)だけど、unshift()は、o(n**2)みたい。
もし、必要なら、push()とreverse()で代替したほうがよいでしょう。
VBScriptの配列をReDimで伸縮するとき、漸減はそれほどでもないが、漸増はとても遅い。
t1 = Timer
ReDim a(1024 * 512)
For k = 1024 * 512 To 1 Step -1
ReDim Preserve a(k - 1)
Next
MsgBox Timer - t1
'1.011719
t1 = Timer
a = Array()
For k = 1 To 1024 * 512
ReDim Preserve a(k)
Next
MsgBox Timer - t1
'31.84375
束で増やすようにすれば、それなりに速くなります。
もし最大値が予想できるなら、最初にどんと大きく作って、最後に小さく調整するとよいでしょう。
ScriptControl経由でJScriptのStringオブジェクトのsplit()メソッドを使う。
split()メソッドの結果は、JScriptのArrayオブジェクトなので、これをVBScriptの配列に変換します。
a=String(1024*1024,"a")
t1=Timer
Set sc=CreateObject("ScriptControl")
sc.Language="JScript"
sc.AddCode "function split(s,p){return s.split(p);}"
set b=sc.CodeObject.split(a,"a")
Dim c()
ReDim c(b.length-1)
k=0
For Each d In b
c(k)=d
k=k+1
Next
MsgBox Timer-t1
'4.148438
For Eachで使う分には、VBScriptの配列にする必要はなく、JScriptのArrayオブジェクトのまま使えば、もっと速い。
a=String(1024*1024,"a")
t1=Timer
Set sc=CreateObject("ScriptControl")
sc.Language="JScript"
sc.AddCode "function split(s,p){return s.split(p);}"
b=sc.CodeObject.split(a,"a")
MsgBox Timer-t1
' 1.609375
JScriptのArrayオブジェクトのまま使うのが、お勧めです。
VBScriptの配列にするなら、代替関数のほうがよいでしょう。
代替関数のほうが、速い。とは、情けない。
a = String(1024 * 1024,"a")
t1 = Timer
b = Splitx(a, "a")
MsgBox Timer - t1
' 7.492188
Function Splitx(s, p)
Dim a(), n, b, e, f
ReDim a(Len(s))
b = 1
n = 0
f = InStr(b, s, p)
Do While f
a(n) = Mid(s, b, f - b)
n = n + 1
b = f + Len(p)
f = InStr(b, s, p)
Loop
a(n) = Mid(s, b)
ReDim Preserve a(n)
Splitx = a
End Function
Split()関数もReplace()関数と同程度に遅いですね。
a = String(1024 * 1024,"a")
t1 = Timer
b = Split(a, "a")
MsgBox Timer - t1
' 32.125
同程度に遅いのでは、Replace()の代替には使えません。
a=String(1024*1024,"a")
t1=Timer
b=Join(Split(a,"a"),"b")
MsgBox Timer-t1
' 33.125
と言うか、こんなに性能が近いのは、Replace()の実装が、実は、Join(Split())だったりして?
それにしても、ひどい実装です。
Replace()関数は、高速なRegExpのReplace()メソッドで代替できるのでよいけれど、Split()関数の代替はどうしましょう?
これも同様です。性能は、中くらい!
なので、これより、"VBScript.RegExp"のReplace()メソッドがお勧めです。
a = String(1024& * 1024&, "a")
t1 = Timer
b = Replacex(a, "a", "b")
Debug.Print Timer - t1
' 2.523438
Function Replacex(s, p, r)
Dim a() As String, n As Long, b As Long, e As Long, f As Long
ReDim a(Len(s))
b = 1
n = 0
f = InStr(b, s, p)
Do While f
a(n) = Mid(s, b, f - b)
n = n + 1
b = f + Len(p)
f = InStr(b, s, p)
Loop
a(n) = Mid(s, b)
ReDim Preserve a(n)
Replacex = Join(a, r)
End Function
VBAのReplace()関数も、VBScriptと同じ実装のようです。というか、VBAが先に遅い実装をした?
a = String(1024& * 1024&, "a")
t1 = Timer
b = Replace(a, "a", "b")
Debug.Print Timer - t1
' 32.875
同様に、"VBScript.RegExp"のReplace()メソッドは、チョー速い!
a = String(1024& * 1024&, "a")
t1 = Timer
Set re = CreateObject("VBScript.RegExp")
re.Global = True
re.Pattern = "a"
b = re.Replace(a, "b")
Debug.Print Timer - t1
' 0.203125
代替関数を作ってはみたものの、性能は、中くらい!(:-p)
RegExpのReplace()メソッドが一番ですね。
a = String(1024 * 1024,"a")
t1 = Timer
b = Replacex(a, "a", "b")
MsgBox Timer - t1
' 5.125
Function Replacex(s, p, r)
Dim a(), n, b, e, f
ReDim a(Len(s))
b = 1
n = 0
f = InStr(b, s, p)
Do While f
a(n) = Mid(s, b, f - b)
n = n + 1
b = f + Len(p)
f = InStr(b, s, p)
Loop
a(n) = Mid(s, b)
ReDim Preserve a(n)
Replacex = Join(a, r)
End Function
ScriptControl経由でJScriptのStringオブジェクトのreplace()メソッドを使う。
a=String(1024*1024,"a")
t1=Timer
Set sc=CreateObject("ScriptControl")
sc.Language="JScript"
sc.AddCode "function replace(s,p,r){return s.replace(p,r);}"
b=sc.CodeObject.replace(a,"a","b")
MsgBox Timer-t1
' 0.625
わざわざ使うほどのことはないですが、HTMLやWSFなどならいいかも。
VBScriptのReplace()関数が遅い?と思ったことありませんか?
そこで試してみました。
置換回数(n)が多いと、o(n**2)で?、遅くなるようです。
a=String(1024*1024,"a")
t1=Timer
b=Replace(a,"a","b")
MsgBox Timer-t1
' 33.125
一方、RegExpのReplace()メソッドは、チョー速い!
a=String(1024*1024,"a")
t1=Timer
Set re=New RegExp
re.Global=True
re.Pattern="a"
b=re.Replace(a,"b")
MsgBox Timer-t1
' 0.1875
RegExpのReplace()メソッドを使いましょう。