前回の二重起動防止は、単に同じアプリケーションが起動されているかどうかを判定することしかできませんでした。そこで今回は、2回目以降の起動時に、すでに起動されている側のプロセスに向けて、新しいプロセスが起動されたことを通知してみます。そして、最初に起動されていた側では、通知を受け取ったら、2つ目のプロセスが起動したことを表示するようにしてみます。
2回目以降に起動されたプロセスからすでに起動されているプロセスに対して何か通知を行うには、初回起動のプロセスが所有するウィンドウにアプリケーション固有のメッセージを送ります。以前も説明した通り、ウィンドウメッセージはいくつもの種類があってそれぞれ役割が決められていますが、以前にも説明したとおり、メッセージコード0x0400 ( ) から0xBFFFまでの範囲についてはアプリケーションが自由にその役割を定義してよいということになっています。これを使用すれば、すでに起動されているプロセスがそのメッセージを取得することで再び起動されたことを知ることができます。
ここで1つ問題が出てきます。ウィンドウにメッセージを送るためのPostMessage関数やSendMessage関数は、その引数として対象となるウィンドウのハンドルを必要とします。すなわち、メッセージを送るためには対象となるウィンドウのハンドルを知らなくてはなりません。
そこで今回は、初回起動かどうかを判別するための名前付きカーネルオブジェクトとして、ミューテックスではなくファイルマッピングオブジェクトを採用し、これを使って共有メモリを作成してしまいましょう。そして、最初に起動されたプロセスのウィンドウハンドルを共有メモリに格納してしまえばよいのです。そうすれば、2回目以降に起動されたプロセスは、その共有メモリからウィンドウハンドルを取得して、そのハンドルの示すウィンドウにメッセージを送ることができます。
メッセージの送信・ポストについては、『メッセージの送信とポスト』の項を参照してください。また、ファイルマッピングオブジェクトについては『プロセス間共有メモリ』の項を参照してください。
ところで、メッセージを渡すにはPostMessage関数によるメッセージのポストとSendMessage関数によるメッセージの送信があります。以前にも説明したとおり、SendMessage関数は、相手側でのメッセージ処理が完了するまで呼び出し元のプログラムの実行を停止して待機します。一方、PostMessage関数はメッセージをポストするのみで、相手のメッセージ処理を待たずに直ちに次の処理を行います。どちらを使用するかはプログラムの動きによって異なってきますが、単純に通知するのみならばPostMessage関数で問題ないでしょう。
大まかな処理の手順はだいたい以下のようになるかと思います。
処理を各部分に分けてみていくことにします。今回使用するWindows APIは以前に説明したものばかりですので、詳しい説明は省きます。
まずは以下の処理をするためのスクリプトです。
; ファイルマッピングオブジェクトの名前の定義 ; (アプリケーション固有の名前にする必要があります) #define MAP_OBJ_NAME "HSP_WinAPI_Test_Mapping_Object" ; ファイルマッピングオブジェクトの作成 sharedMemSize = 1024 hMapObj = CreateFileMapping(-1,0,PAGE_READWRITE,0,sharedMemSize,MAP_OBJ_NAME) ; ファイルマッピングオブジェクトが作成されていたかどうかの判定 if (GetLastError() == ERROR_ALREADY_EXISTS) { ; すでに同じ名前のオブジェクトが存在する goto *SecondRunProc } else { ; オブジェクトが新しく作成された goto *FirstRunProc }
ファイルマッピングオブジェクトは、同じ名前を指定すればどのプロセスからでもそのオブジェクトを操作できてしまいます。そのため、これらの名前はアプリケーション固有のものにして、他のアプリケーションが使うオブジェクトと重ならないようにしましょう。
ファイルマッピングオブジェクトが作成されていなかった場合(初回起動の場合)の処理です。
; プログラム起動通知用ウィンドウメッセージを定義 #define MYWM_RUN_NOTIFY (WM_USER + 1) *FirstRunProc ; プログラム終了時処理の登録 onexit gosub *OnAppQuit ; 割り込み処理メッセージの登録 oncmd gosub *OnRunNotify, MYWM_RUN_NOTIFY ; ビューのマッピング sharedMemPtr = MapViewOfFile(hMapObj,FILE_MAP_WRITE,0,0,sharedMemSize) ; ウィンドウハンドルを共有メモリに格納 dupptr sharedMemVal, sharedMemPtr, sharedMemSize sharedMemVal(0) = hwnd ; ビューのマッピング解除 UnmapViewOfFile sharedMemPtr sharedMemPtr = 0 dim sharedMemVal, 1 stop *OnRunNotify ; 二重起動通知メッセージを受け取ったときの処理 gsel 0, 2 dialog "起動されました" gsel 0, 1 return *OnAppQuit ; ファイルマッピングオブジェクトのクローズ(終了時) CloseHandle hMapObj end
今回はアプリケーション固有のウィンドウメッセージコードとしてMYWM_RUN_NOTIFYという定数名を定義しています。これは、プログラム起動を通知する際に使用するものです。ここで使用する値は0x0400 ( ) から0xBFFFの範囲にする必要があります。それ以外のものを使うと、動作がおかしくなる可能性があるので注意してください。
マッピングオブジェクトが作成されていた場合(2回目以降の起動の場合)の処理です。
*SecondRunProc ; ビューのマッピング sharedMemPtr = MapViewOfFile(hMapObj,FILE_MAP_READ,0,0,sharedMemSize) ; 共有メモリからウィンドウハンドル取得 dupptr sharedMemVal, sharedMemPtr, sharedMemSize hwndTarget = sharedMemVal(0) ; ビューのマッピング解除 UnmapViewOfFile sharedMemPtr sharedMemPtr = 0 dim sharedMemVal, 1 ; メッセージをポスト PostMessage hwndTarget, MYWM_RUN_NOTIFY, 0, 0 ; ファイルマッピングオブジェクトのクローズ CloseHandle hMapObj end
; Windows API 関数定義 #uselib "kernel32.dll" #func CloseHandle "CloseHandle" sptr #cfunc CreateFileMapping "CreateFileMappingA" sptr,sptr,sptr,sptr,sptr,sptr #cfunc GetLastError "GetLastError" #cfunc MapViewOfFile "MapViewOfFile" sptr,sptr,sptr,sptr,sptr #func UnmapViewOfFile "UnmapViewOfFile" sptr #uselib "user32.dll" #func PostMessage "PostMessageA" sptr,sptr,sptr,sptr #func SetForegroundWindow "SetForegroundWindow" sptr ; Windows API 定数定義 #define FILE_MAP_WRITE 2 #define FILE_MAP_READ 4 #define PAGE_READWRITE 4 #define ERROR_ALREADY_EXISTS 183 #define WM_USER 0x0400 ; プログラム起動通知用ウィンドウメッセージを定義 #define MYWM_RUN_NOTIFY (WM_USER + 1) ; ファイルマッピングオブジェクトの名前の定義 ; (アプリケーション固有の名前にする必要があります) #define MAP_OBJ_NAME "HSP_WinAPI_Test_Mapping_Object" ; ファイルマッピングオブジェクトの作成 sharedMemSize = 1024 hMapObj = CreateFileMapping(-1,0,PAGE_READWRITE,0,sharedMemSize,MAP_OBJ_NAME) ; ファイルマッピングオブジェクトが作成されていたかどうかの判別 if (GetLastError() == ERROR_ALREADY_EXISTS) { ; すでに同じ名前のオブジェクトが存在する goto *SecondRunProc } else { ; オブジェクトが新しく作成された goto *FirstRunProc } *FirstRunProc ; プログラム終了時処理の登録 onexit gosub *OnAppQuit ; 割り込み処理メッセージの登録 oncmd gosub *OnRunNotify, MYWM_RUN_NOTIFY ; ビューのマッピング sharedMemPtr = MapViewOfFile(hMapObj,FILE_MAP_WRITE,0,0,sharedMemSize) ; ウィンドウハンドルを共有メモリに格納 dupptr sharedMemVal, sharedMemPtr, sharedMemSize sharedMemVal(0) = hwnd ; ビューのマッピング解除 UnmapViewOfFile sharedMemPtr sharedMemPtr = 0 dim sharedMemVal, 1 stop *OnRunNotify ; 二重起動通知メッセージを受け取ったときの処理 gsel 0, 2 dialog "起動されました" gsel 0, 1 return *OnAppQuit ; ファイルマッピングオブジェクトのクローズ(終了時) CloseHandle hMapObj end *SecondRunProc ; ビューのマッピング sharedMemPtr = MapViewOfFile(hMapObj,FILE_MAP_READ,0,0,sharedMemSize) ; 共有メモリからウィンドウハンドル取得 dupptr sharedMemVal, sharedMemPtr, sharedMemSize hwndTarget = sharedMemVal(0) ; ビューのマッピング解除 UnmapViewOfFile sharedMemPtr sharedMemPtr = 0 dim sharedMemVal, 1 ; メッセージをポスト PostMessage hwndTarget, MYWM_RUN_NOTIFY, 0, 0 ; ファイルマッピングオブジェクトのクローズ CloseHandle hMapObj ; 対象ウィンドウを前面に表示 SetForegroundWindow hwndTarget end
上のスクリプトをいくつか同時に起動させてみましょう。2つ目、3つ目と起動されていくたびに、最初に起動されたプロセスのウィンドウに、起動されたことを示す文字列が表示されていくことと思います。
今回はメッセージを送るだけでしたが、メッセージを送る際には2つの4バイトデータをwParamパラメータ、lParamパラメータとして送ることができますし、それ以上のサイズのデータ(例えばファイル名など)も共有メモリを使えば送ることができるでしょう。いろいろと各自で試してみてください。