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 BlockLocal Const $WAIT_OBJECT_0 = 0Local Const $WAIT_ABANDONED = 0x80 ; 0x80 in hexLocal Const $WAIT_TIMEOUT = 0x102 ; 0x102 in hexLocal Const $WAIT_FAILED = -1 ; 0xFFFFFFFF in hex$handle = Singleton( @ScriptName,1)If @error = $ERROR_ALREADY_EXISTS Then Block( $handle )EndIfMsgBox(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 $hEndFunc ; => 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