Dies Aliquanti

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

AutoItで,アプリの多重起動の制御

Windows上のプログラムで複数起動を禁止したい場面が時々あります.今回は「禁止」ではなく,他のインスタンスが終了するまで,「待つ」機能が必要になりました.
 以前,よく使っていたUWSCでは,こんな記事もあります.こういう処理は本来,OSが用意するMutexを使うのが,「正道」でしょうが,件の記事は,UWSCの機能の範囲で実装しようとしています.

AutoIt(v3.3.0)では,どうするのか調べてみたら,FAQの中に,ユーザー定義関数の_Singleton()という関数を使え,と書いてあります._Singleton()関数のサンプルコードには,ほかのインスタンスが起動していたらメッセージボックスを出して終了する,という例が出ています.
 今回,作りたいのは,ほかのインスタンスが起動していたら,それが終了するまで待って動作する機能です._Singleton()を定期的に(ダサい)呼び出して,0(=失敗)以外が帰ってくるまで待ち続けるようにしましたが,うまくいきません.orz

 _Singleton()関数のソースコードを見てみると,Win32.DLLのCreateMutexを使っているようです.Googleとかで調べてみると,CreateMutexと組み合わせて,待つためにはWin32.DLLのなかのWaitForSingleObject()とかを使うようですが,このような関数はAutoItの中にはありません.そもそも_Singleton()関数はMutexの獲得に失敗したときは0を返してしまうので,その先どうしようもありません. orz

 どう考えても_Singleton()関数の仕様がヘンだと思うのですが,ソースコードをもう一度見てみると,ちょっと直せば何とかなりそうな気がしてきたので一箇所修整して,Mutexの獲得が出来た/出来ないにかかわらず,Mutxのhandleを返すことにしました。Mutexの獲得に関しては,@Errorシステム変数を通じて情報を得ることにします.
 また,Wind32.DLLのWaitForSingleObject()を呼び出すために関数Block()も作ります.DLLの呼び出しもAutoItで試すのは初めてですが,出来てしまえばなんということはありません.
 なお,Mutexの所有権を明示的に解放するためには,ReleaseMutex()という関数がWin32.DLLにありますが,プログラムの終了とともに自動的に解放されるようです.

 で,テストでつくったプログラムを最後にのせます.バッチファイル中から複数起動してもダイアログは一度に1つしかでません(5秒で自動的に消えます).

===================

#include <Misc.au3>
#include <WinAPI.au3>

; @Error code after Singleton()
Local Const $ERROR_ALREADY_EXISTS = 183
;return value of Block
Local Const $WAIT_OBJECT_0 = 0
Local Const $WAIT_ABANDONED = 0x80 ; 0x80 in hex
Local Const $WAIT_TIMEOUT = 0x102 ; 0x102 in hex
Local Const $WAIT_FAILED = -1 ; 0xFFFFFFFF  in hex


$handle = Singleton( @ScriptName,1)
If @error = $ERROR_ALREADY_EXISTS Then
 Block( $handle )
EndIf
MsgBox(0, "Test for Singleton & Block() " & @SEC & @MSEC , "Press OK", 5)
Exit

;Block( $handle, $timeOut )
;  check mutex for $handle
;      when $timeOut is
;       -1 (default) : waits until the mutex of handle can be grabed
;  0 :  returns promptly
;       positive value : wait in msec
;     @Error is available after call
;
Func Block( $handle, $timeOut = -1 )
 ;MsgBox(0, "Block() called", "Block() called")
 $h = DllCall("kernel32.dll", "int64", "WaitForSingleObject", "ptr", $handle, "ptr", $timeOut)
 $lastError = DllCall("kernel32.dll", "int", "GetLastError")
 If $lastError[0] = $ERROR_ALREADY_EXISTS Then
  Return SetError($lastError[0], $lastError[0], $h)
 EndIf
 Return $h
EndFunc ; => Block

;
; Delived from _Singleton() in Misc.au3
;
Func Singleton($sOccurenceName, $iFlag = 0)
 Local Const $ERROR_ALREADY_EXISTS = 183
 Local Const $SECURITY_DESCRIPTOR_REVISION = 1
 Local $handle, $lastError, $pSecurityAttributes = 0

 If BitAND($iFlag, 2) Then
  ; The size of SECURITY_DESCRIPTOR is 20 bytes.  We just
  ; need a block of memory the right size, we aren't going to
  ; access any members directly so it's not important what
  ; the members are, just that the total size is correct.
  Local $structSecurityDescriptor = DllStructCreate("dword[5]")
  Local $pSecurityDescriptor = DllStructGetPtr($structSecurityDescriptor)
  ; Initialize the security descriptor.
  Local $aRet = DllCall("advapi32.dll", "int", "InitializeSecurityDescriptor", _
    "ptr", $pSecurityDescriptor, "dword", $SECURITY_DESCRIPTOR_REVISION)
  If Not @error And $aRet[0] Then
   ; Add the NULL DACL specifying access to everybody.
   $aRet = DllCall("advapi32.dll", "int", "SetSecurityDescriptorDacl", _
     "ptr", $pSecurityDescriptor, "int", 1, "ptr", 0, "int", 0)
   If Not @error And $aRet[0] Then
    ; Create a SECURITY_ATTRIBUTES structure.
    Local $structSecurityAttributes = DllStructCreate("dword;ptr;int")
    ; Assign the members.
    DllStructSetData($structSecurityAttributes, 1, DllStructGetSize($structSecurityAttributes))
    DllStructSetData($structSecurityAttributes, 2, $pSecurityDescriptor)
    DllStructSetData($structSecurityAttributes, 3, 0)
    ; Everything went okay so update our pointer to point to our structure.
    $pSecurityAttributes = DllStructGetPtr($structSecurityAttributes)
   EndIf
  EndIf
 EndIf

 $handle = DllCall("kernel32.dll", "int", "CreateMutexA", "ptr", $pSecurityAttributes, "long", 1, "str", $sOccurenceName)
 $lastError = DllCall("kernel32.dll", "int", "GetLastError")
 If $lastError[0] = $ERROR_ALREADY_EXISTS Then
  If BitAND($iFlag, 1) Then
   Return SetError($lastError[0], $lastError[0], $handle[0])  ; !!! 変更したところ !!!
  Else
   Exit -1
  EndIf
 EndIf
 Return $handle[0]
EndFunc   ;==> Singleton



コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://diesaliquanti.blog.fc2.com/tb.php/604-63a02ad5
この記事にトラックバックする(FC2ブログユーザー)

FC2Ad

まとめ

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。