タグ:VBScript ( 58 ) タグの人気記事
[VBScript] 非同期処理(マルチスレッド) 参考ページ
どーもボキです。

More (長いので)
[PR]
by yozda | 2010-03-15 22:57 | プログラミング | Trackback | Comments(0)
[VBScript] 非同期処理(マルチスレッド)は実現でけた!
どーもボキです。

でけた。TTimerを使えばよかった。

TThread.Execute内(スレッド)で実行すると、Invokeがうまく処理できないが、
スレッド以外でInvokeすれば、うまく処理できる。
なら、TTimerで処理したらどうだろう?とやってみたら、うまくいった。

下図のように、メインプロセスはInputBox(「コマンド入力」ダイアログ)で止まっているが、
バックグラウンド(cscript)では、Func_Inc1、Func_Inc5、Func_Inc10 が実行されていることがわかる。

正確にはタイマーじゃけどね。そもそも用途がなかったわ。
a0021757_17542871.gif
サンプルプログラム

サンプルの使い方
 1.SampleActiveXの登録.bat を実行する (SampleActiveX.dllの登録)
 2.ActiveX_DLL.vbs を実行


SampleActiveX.dllの関数
 ・関数の登録 : SetFunc GetRefでの関数ポインタ, 関数名, 関数実行間隔[msec]
 ・関数の実行 : ExeFunc 関数の登録インデックス (0~)

VBSのソース
Set ActiveX_DLL = CreateObject("SampleActiveX.CoClass")

GVal = 0

ActiveX_DLL.SetFunc GetRef("Func_Inc1" ),"Func_Inc1" , 1000
ActiveX_DLL.SetFunc GetRef("Func_Inc5" ),"Func_Inc5" , 5000
ActiveX_DLL.SetFunc GetRef("Func_Inc10"),"Func_Inc10",10000

ActiveX_DLL.ExeFunc 0
ActiveX_DLL.ExeFunc 1
ActiveX_DLL.ExeFunc 2

While(1)
Execute InputBox("コマンド?","コマンド入力","MsgBox GVal")
WEnd

'関数の定義 ---------------------------------------------------
Sub Func_Inc1
GVal = GVal +1
WScript.Echo "VBS:Func_Inc1 実行 GVal=" & GVal
End Sub

Sub Func_Inc5
GVal = GVal +5
WScript.Echo "VBS:Func_Inc5 実行 GVal=" & GVal
End Sub

Sub Func_Inc10
GVal = GVal +10
WScript.Echo "VBS:Func_Inc10 実行 GVal=" & GVal
End Sub


Delphiソース
unit uCoClass;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
Windows,
ExtCtrls, // TTimer
SysUtils, Variants, ComObj, ComServ, ActiveX, SampleActiveX_TLB, StdVcl;

type
TVBSFunc = Record
FuncDisp : IDispatch;
FuncName : OleVariant;
Timer : TTimer;
end;

type
TCoClass = class(TAutoObject, Interface_)
protected
disp : IDispatch;
FuncList : Array of TVBSFunc;
procedure SetFunc(var FuncDisp: OleVariant; FuncName: OleVariant;
ExecInterval: Integer); safecall;
procedure ExeFunc(FuncIndex: Integer); safecall;
private
procedure _OnTimer(Sender : TObject);
end;

implementation



procedure TCoClass.SetFunc(var FuncDisp: OleVariant; FuncName: OleVariant;
ExecInterval: Integer);
var
idx : Integer;
begin
idx := Length(FuncList);
SetLength(FuncList, idx+1);

FuncList[idx].FuncDisp := FuncDisp;
FuncList[idx].FuncName := FuncName;
FuncList[idx].Timer := TTimer.Create(nil);
FuncList[idx].Timer.Enabled := False;
FuncList[idx].Timer.Interval := ExecInterval;
FuncList[idx].Timer.Tag := idx; // 格納インデックスをTagに格納
FuncList[idx].Timer.OnTimer := _OnTimer;
end;



procedure TCoClass._OnTimer(Sender: TObject);
const
IID_NULL : TGUID='{00000000-0000-0000-0000-000000000000}';
var
fidx : Integer;
disp : IDispatch;
wsname : WideString;
szName: POleStr;
dispID: TDispID;
varParam, varChars: Variant;
params : DISPPARAMS;
begin
// Tagから格納インデックスを取得
fidx := TTimer(Sender).Tag;

// 格納情報を取得
disp := FuncList[fidx].FuncDisp;
wsname := FuncList[fidx].FuncName;
szName := @(wsname[1]);

// 関数のマッピング
if FAILED(disp.GetIDsOfNames(IID_NULL, POleStrList(@szName), 1,
LOCALE_SYSTEM_DEFAULT, PDispIDList(@dispID))) then Exit;

// 引数の設定
varParam := 0;
params.rgvarg := @varParam;
params.rgdispidNamedArgs := nil;
params.cArgs := 0;
params.cNamedArgs := 0;

// 関数の実行
if FAILED(disp.Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, params, @varChars, nil, nil)) then Exit;
end;



procedure TCoClass.ExeFunc(FuncIndex: Integer);
begin
if FuncIndex < 0 then exit;
if FuncIndex > High(FuncList) Then exit;

// タイマーを実行
FuncList[FuncIndex].Timer.Enabled := True;
end;



initialization
TAutoObjectFactory.Create(ComServer, TCoClass, Class_CoClass,
ciMultiInstance, tmApartment);
end.

[PR]
by yozda | 2010-03-14 17:43 | プログラミング | Trackback | Comments(0)
[VBScript] 非同期処理(マルチスレッド)は実現不可能
どーもボキです。

タイトル通り、VBScriptでのスレッドは無理やね。

VBScriptのGetRef("関数名")で取得した関数ポインタ(IDispatch型)を
Delphiで作ったActiveX DLLに渡し、その関数ポインタをスレッド処理させても無理。

IDispatch.Invoke を スレッド以外で呼び出すと正常に処理できるが、
スレッドで(TThread.Execute)で実行すると、Invoke実行タイミングでスレッド処理が止まる。

変数ならば、スレッドでも処理できることは確認できた。
けど、やりたかったことは、VBScript内で作成した関数のスレッド処理。
変数だけがスレッド処理できても意味がない。


インタプリタのスクリプト言語だから、これが限界で、これが当たり前なのかな。
作ったサンプルは、こんどマトメてあげる。
[PR]
by yozda | 2010-03-10 01:32 | プログラミング | Trackback | Comments(0)
[VBScript] 非同期処理(マルチスレッド)は実現可能か?
どーもボキです。

ここのまるっとコピペ。
1.複数のスクリプトを作成し一方のスクリプトから実行する
  一番一般的で簡単です。複数のファイルをまとめて扱う必要があります。
  2つ3つでは問題ありませんが、10個、20個ともなれば少々手間です。

2.1つのスクリプトから %TEMP% フォルダにスクリプトを別途作成して実行する
  1つのファイルなので完成したら扱いやすいですが、スクリプトを作るスクリプトを記述する必要があります。
  また、生成したスクリプトを削除するコードを書く必要がありますが、途中で停止された場合ごみが残ります。

3.1つのスクリプトを引数によって挙動を変えて実行する
  下のような感じで。1つのスクリプトで引数により実行する関数を変更します。これにより1と2の欠点を解消します。
以下のソースは、3に該当する。
けどこれって、自身のスクリプトを別WScriptプロセスで実行してるだけだから、
変数の共有化とかできないよね。まぁ非同期処理っちゃぁ非同期だけどもさ。
やっぱ、VBScriptでスレッド処理って無理なのかな?
Option Explicit
Dim ProcNumber, WshShell
Set WshShell = CreateObject("WScript.Shell")
If WScript.Arguments.Count = 0 Then
ProcNumber = 0
Else
ProcNumber = CInt(WScript.Arguments(0))
End If

Select Case ProcNumber
Case 0
Call Proc0
Case 1
Call Proc1
Case Else
WScript.Echo "else"
End Select

WScript.Quit

Sub Proc0
' Proc1 を別プロセスで非同期に実行する
ProcNumber = 1
WshShell.Exec "wscript " & WScript.ScriptName & " " & ProcNumber
WScript.Echo "Main"
End Sub
Sub Proc1
WScript.Sleep 3000
WScript.Echo "second"
End Sub

[PR]
by yozda | 2010-03-03 23:55 | プログラミング | Trackback | Comments(0)
[VBScript] 指定した要素の配列内インデックスを取得する
どーもボキです。

配列内の要素のインデックスを調べたいとき、For文でひとつひとつ確認するのはメンドクサイ。
なので作った。指定した要素の配列内インデックスを取得する関数を。

考え方は、
1.Join()で、配列を一つの文字列に変換する
2.Left()で、1の文字列から、インデックスを得たい文字の位置までを抜き出す
3.Split()で、2の文字列を再度配列に変換し、要素数を取得する
 → 要素のインデックス
ってな感じ。

' 配列内の該当インデックスを返す 見つからなければ GetIndex=-1
GetIndex_Delimiter = ","
Function GetIndex(ByRef CheckArray,CheckValue)
Dim s,t,idx,d

GetIndex = -1
If Not IsArray(CheckArray) Then Exit Function
d = GetIndex_Delimiter

s = d & Join(CheckArray,d) & d ' 先頭&最後データのInStr検出抜け回避
t = d & CheckValue & d ' 〃
idx = InStr(s,t)
If idx = 0 Then Exit Function ' CheckValueなし
If idx = 1 Then :GetIndex = 0 :Exit Function

s = Left(s,idx-1)
GetIndex = UBound(Split(s,d))
End Function
※配列内に文字列が含まれ、かつその中に「,(半角カンマ)」が含まれる場合、この関数は、正常に動作しない。
 その場合は、GetIndex_Delimiter に適当な文字列を指定すること。
 VBScriptには引数へのOptionalオプション(なくてもよい引数)がないため、この方法にしている。
 引数にDelimiter入れて、毎回指定するってムダだし。
 また、GetIndex_Delimiter = "ここが区切り" とか、要素データとしてありえない文字列を指定すればいいんだけど、美しくないからやめた。
[PR]
by yozda | 2010-01-18 21:48 | プログラミング | Trackback | Comments(0)
[VBScript] ラジオボタンダイアログを実現する
どーもボキです。

より改良したものはこちら

VBScript(WSH)には、ユーザインターフェースと呼べるもんがほとんどない。
ユーザ入力画面といえば、InputBox か MsgBox くらい。
InputBox関数を使えばある程度の情報を入力させることが出来るが、せめてラジオボタンでの選択画面くらいはほしいもの。

VBScript単体での実現は無理だが、
VBScriptで作成したIEオブジェクトを利用すれば、ラジオボタンダイアログが実現できる。

ソースもアップしたかったが、エキサイトブログは、onclick や <Form>があると投稿できんし、
<INPUT>があると、オブジェクトが表示されるから、あきらめた。

くわしくはソース参照。IEオブジェクト作って、それにラジオボタン表示して、ユーザの操作結果を取得してるだけ。
RItems = Array( _
"ラジオボタンのタイトル 1", _
"ラジオボタンのタイトル 2", _
"ラジオボタンのタイトル 3", _
"ラジオボタンのタイトル 4", _
"ラジオボタンのタイトル 5" _
)


' 関数実行
' Function RadioGroupBox(sTitle, sCaption, RadioItems, DefaultIndex)
' sTitle : タイトル
' sCaption : メッセージ
' RadioItems : ラジオアイテムリスト (配列)
' DefaultIndex : デフォルト選択するアイテムインデックス
r = RadioGroupBox("タイトル", "キャプション", RItems, 0)

WScript.Echo "インデックス = " & r

a0021757_23513681.gif
実行イメージ

IEオブジェクトを使えば、大抵のユーザインターフェースをIE側で作れるだろね。

けど、これ以上複雑なもんは、DelphiやらCやら専用開発環境で作ったほうがいいと思う。
HTA(HTML アプリケーション)の知識も要るし。スクリプト言語で複雑な処理したいがために、HTAやらを調べ上げるのってムダじゃないかな。

スクリプト処理って、シンプルに作れてなんぼだと思うわ。

11.02.17 ソースを掲載
[PR]
by yozda | 2010-01-16 23:51 | プログラミング | Trackback(1) | Comments(0)
[VBScript] iniファイルをDelphi風(TIniFile)に処理する
どーもボキです。

iniファイルとは、以下のように記述されたテキストファイルのこと。
アプリの設定情報の保存などに良く使われている。
[セクション1]
キー1=値
キー2=値
キー3=値
Delphi の TIniFile風に使えるクラスを作った。

DelphiではCreateでオブジェクトを作成するが、このクラスではファイルを指定する意味で使うので、
OpenFile という名前のSubプロシージャにしている。

【使い方】
Dim ini
Set ini = New TIniFile

' iniファイルを開く
ini.OpenFile("D:\テスト.ini")

' iniファイルを読み込む
WScript.Echo ini.ReadValue("sec3","key10",-1)

For i = 1 To 3
For j = 1 To 10
' iniファイルに書き込む
ini.WriteValue "sec" & i,"key" & j, (i-1)*10+j
Next
Next

【TIniFileクラスのプログラム】
Class TIniFile
Private objFS
Private FPath
Private Lines

' iniファイルを開く
' FilePath : ファイルパス
Public Sub OpenFile(FilePath)
Dim file

Set objFS = CreateObject("Scripting.FileSystemObject")
FPath = FilePath
If objFS.FileExists(FPath) Then
Set file = objFS.OpenTextFile(FPath ,1)
Lines = Split(File.ReadAll,vbNewLine,-1,vbTextCompare)
file.Close
Else
Redim Lines(0)
End If
End Sub

' iniファイルを読み込む
' sSec : セクション名
' sKey : キー名
' DefValue : キー名が存在しなかった場合の戻り値
' 戻り値 : 読み込んだ値
Public Function ReadValue(sSec,sKey,DefValue)
Dim i,s,b

b = False
ReadValue = DefValue
For i = 0 To UBound(Lines) -1:Do
s = Replace(Replace(Lines(i),vbCR,""),vbLF,"")
If (Left(s,1) = "[") and (Right(s,1) = "]") Then
If b and (s <> "[" & sSec & "]") Then Exit Function
b = (s = "[" & sSec & "]")
End If
If Not b Then Exit Do
If InStr(s,sKey & "=") <> 1 Then Exit Do

ReadValue = Right(s,Len(s)-InStrRev(s,"="))
Loop Until 1 :Next
End Function

' iniファイルに書き込む
' sSec : セクション名
' sKey : キー名
' DefValue : 書き込む値
' ※WriteValue実行毎に、FilePathで指定したファイルを更新(作成)する
Public Sub WriteValue(sSec,sKey,Value)
Dim i,s,s_out,b_sec,b_out,file

b_sec = False
b_out = False
s_out = sKey & "=" & Value
Set file = objFS.OpenTextFile(FPath, 2, True)
For i = 0 To UBound(Lines) -1
s = Replace(Replace(Lines(i),vbCR,""),vbLF,"")
If (Left(s,1) = "[") and (Right(s,1) = "]") Then
If b_sec Then
If Not b_out Then
file.WriteLine s_out
b_out = True
End If
Else
b_sec = (s = "[" & sSec & "]")
End If
file.WriteLine s
ElseIf b_sec and InStr(s,sKey & "=") = 1 Then
file.WriteLine s_out
b_out = True
Else
file.WriteLine s
End If
Next
If Not b_sec Then file.WriteLine "[" & sSec & "]"
If Not b_out Then file.WriteLine s_out
file.Close
OpenFile(FPath)
End Sub
End Class


TIniFileのようなクラスを作っておけば、
「あるiniファイルの内容を別のiniファイルに反映する」 といった作業がとてもやりやすくなる。

今回は、慣れているDelphiの処理方法をそのままVBSで実現してみた。
ReadValue時に、セクション名&キー名の大文字・小文字を区別するので注意。
大文字・小文字の区別がイヤなら、文字を読み出す前にLCase/UCaseでどちらかに統一しておけばいい。

VBScriptには構造体がないが、クラスを使うことで構造体を代用することが出来る。
クラスって便利だ。
[PR]
by yozda | 2010-01-02 01:33 | プログラミング | Trackback | Comments(0)
[VBScript] ループ制御コマンド「Continue」っぽいものを美しく実装する
どーもボキです。

VBScript には、Continue がない。
実現できないコード

For i = 0 To Count
If ループを飛ばしたい条件 Then Continue

ループ内部での処理
Next



同等の処理、これなら実現できる


For i = 0 To Count
If ループ内で処理したい条件 Then
ループ内部での処理
End If
Next
インデントが深くなるのは好みでない。

調べると、For と Do を組み合わせれば、
Continue と同じ機能が実現できることがわかった。そのコードは以下。
Doループ & Exit Do をつかって、Continue を実現した例

For i = 0 To Count
Do
If ループを飛ばしたい条件 Then Exit Do

ループ内部での処理
Loop Until 1
Next
一回のみループするDoをFor内に用意し、ループ処理の実装はそのDoループ内に記述する。
Forループは、Doループを実行するだけ。

けど、Continueを実装するためだけに、インデントが2階層になるのはいただけない。
Do でインデントしなければ、インデントを2階層にしてなくてもいいが、見た目的にいや。
Doループでインデントしなかった場合

For i = 0 To Count
Do
If ループを飛ばしたい条件 Then Exit Do

ループ内部での処理
Loop Until 1
Next


これを解決するの方法が以下
「:」を利用し For と Do を同じ行に記述

For i = 0 To Count: Do
If ループを飛ばしたい条件 Then Exit Do

ループ内部での処理
Loop Until 1: Next
「:」を使えば、For と Do を一行に記述可能。
あーなんて美しいんでしょ。

美しいコードを書き、悦に入る。それが、この仕事の醍醐味。

[PR]
by yozda | 2009-11-27 02:35 | プログラミング | Trackback | Comments(0)
[Delphi] スクリプト言語を利用し、ユーザによる処理実装を実現する方法 その3 : サンプルプログラム2
どーもボキです。

ScriptControl で VBスクリプト でループ処理中も、EXEに触れるようにしたサンプルを作った。
サンプルプログラム

Forループを組んだスクリプトを実行しても、EXEのボタンに触れることが確認できると思う。
一見スレッド処理に見えるかもだが、スクリプトからApplication.ProcessMessage を呼んでるだけ。つまり、単純な一筆書き処理。

EXEで実行させるスクリプトは以下。

下線部分のAutoObject.CallProcessMessageが、Application.ProcessMessage に相当する。
スクリプトでループ処理を行う場合、このサンプルのようにスクリプト内で時々EXEの処理に戻してやる必要がある。
Function Func_VBS(ByRef i)
for i = 0 to 99
AutoObject.Value = AutoObject.Value + 1
AutoObject.CallProcessMessage
Sleep(100)
Next
End Function

また、WScriptオブジェクトが実装されてないので、
WScript.Sleep や WScript.Quit いったフロー管理メソッドが使えない。これは痛い。

サンプルEXEのスクリプトにはSleepを呼び出しているが、それは以下のコードを実行させている。
Private Sub Sleep(mSec)
On Error Resume Next
GetObject( _
"winmgmts:{impersonationLevel=impersonate}").ExecNotificationQuery _
("select * from __instancecreationevent within 1" _
& " where targetinstance isa 'Win32_Process'" _
& " and targetinstance.ProcessID=0" _
).NextEvent mSec
On Error GoTo 0
End Sub
WScript.Quit の代用は、End Sub とか End Function しかなさそうね。
あー使いにく。

参考
 Scripting Your Delphi Applications (タイプライブラリエディタの設定方法とか)
[PR]
by yozda | 2009-10-06 22:43 | プログラミング | Trackback | Comments(0)
[Delphi] スクリプト言語を利用し、ユーザによる処理実装を実現する方法 その2 : サンプルプログラム
どーもボキです。

PPA と ScriptControl で サンプルプログラムを作った。

将来的に実現したい機能は、
 「EXEの変数をスクリプトに渡し、スクリプト内でループ処理し途中結果をEXEに返す」
ってこと。
いきなりすべてを実現するサンプルを作るのは難しいので、まずはループ処理の実装から。

サンプルプログラムを作ってわかったことは、PPAは予想に反して使いやすい。
むしろPPAなら、やりたいことは現仕様のままで実現できそう。
反面ScriptControl は、まだ未知の部分もあるものの、使い物になりそうもない印象。詳細は以下。

Project-PPA (Pascalスクリプト) PPAサンプルプログラム
 良い意味で予想を裏切ってくれたPPA。理由は以下。

  ○スレッド処理を実装済み
   PPAがスレッド実行されるので、スクリプトで無限ループになっても、EXEがフリーズしない。

  ○登録した変数がスクリプト内で変更されるとイベントが発生する
   登録変数の更新毎にイベントが発生するので、EXEとの連動がとりやすい

  ○登録した変数値の変更は、スクリプト内のループ処理中でも可能
   スクリプトの処理位置に関わらず、常にEXEからスクリプトに最新値を渡すことができる

  ○スクリプトの実行を即座に中断できる
   スクリプトの実行状態に関わらず、EXEから即座にスクリプトの中断できる。無限ループに入っても安心。

ScriptControl (VBスクリプト) ScriptControlサンプルプログラム1
 想像以上に難儀だったScriptControl。理由は以下。

  ●スクリプトの実行自体がスレッドで処理できない。正確には、やり方がわからない。
   ScriptControlサンプルプログラム2を見てもらえばわかるように、
   スクリプトの実行部(ScriptControl.Run)をSynchronizeで実行させる必要があるため、スレッドにする意味がない。
   スレッド処理するには、ループ文はThread.Execute、ループ内処理はSynchronizeで処理させる必要がある。(参考)
   VBScriptでループ処理を記述した場合、ソレが出来ない。無限ループを含んだスクリプトを実行させるとEXEがフリーズする。
   EXEのフリーズを回避するため、ScriptControlサンプルプログラム1では、スレッドでVBS用フォームをShowModalで呼ぶようにした。(下図)
a0021757_1113344.gif

  ●EXE ⇔ スクリプト 間のデータやり取りが面倒
   方法1 : スクリプト内の関数の引数 と その戻り値 として渡す方法
     戻り値で結果を受け取るので、当然受け取れるデータはひとつのみ。
     また、結果が得られるのは、関数処理が終了したタイミングのみ。

   方法2 : ActiveXオブジェクトにデータやり取り用のプロパティを作成する方法
     サンプルプログラムの「5-Exposing Objects」 と 詳細説明 (英語)
     1からのクラスを作成に近い。やり取りさせるデータを明確にしなければ、オブジェクトのメンテだけで大変そう。
     この方法でも、スクリプト内に オブジェクトにアクセスさせる処理を記述する必要があるため、
     スクリプト と EXE処理 の完全な役割分担が出来ない。スクリプトからEXEの動作を管理してやる必要がある。

ScriptControlを使う線はほぼなくなったけど、もう少しだけScriptControl をもう少し勉強してみよう。
VBSで処理が実装できれば、スクリプトのデバッグ面で強みになるからね。

 
[PR]
by yozda | 2009-10-03 01:07 | プログラミング | Trackback | Comments(0)