前回の二重起動防止は、単に同じアプリケーションが起動されているかどうかを取得することしかできませんでした。そこで今回は、2回目以降の起動時に、すでに起動されているほうのプロセスに起動されたことを知らせてみましょう。最初に起動されていたほうでは、知らせを受けたら、2つ目のプロセスが起動したことを表示するようにしてみます。
2回目以降に起動されたプロセスからすでに起動されているプロセスに対して、新しいプロセスが起動されたことを知らせるには、初回起動のプロセスが所有するウィンドウにアプリケーション固有のメッセージを送ります。以前も説明した通り、ウィンドウメッセージはいくつもの種類があってそれぞれ役割が決められていますが、0x0400 (
) 以降のメッセージについてはアプリケーションが自由にその役割を定義してよいということになっています。これを使用すれば、すでに起動されているプロセスがそのメッセージを取得することで再び起動されたことを知ることができます。ここで1つ問題が出てきます。ウィンドウにメッセージを送るためのAPI関数であるPostMessage関数やSendMessage関数は、その引数として対象となるウィンドウのハンドルを必要とします。すなわち、メッセージを送るためには対象となるウィンドウのハンドルがわからなくてはなりません。
そこで今回は、初回起動かどうかを判別するための名前付きカーネルオブジェクトとしてファイルマッピングオブジェクトを採用して、これを使って共有メモリを作成してしまいましょう。そして、その共有メモリに、最初に起動されたプロセスのウィンドウハンドルを格納してしまえばいいのです。そうすれば、2回目以降に起動されたプロセスは、その共有メモリからウィンドウハンドルを取得して、そのハンドルの示すウィンドウにメッセージを送ることができます。
メッセージの送信・ポストについては、『メッセージの送信とポスト』の項を参照してください。また、ファイルマッピングオブジェクトについては『共有メモリを使ってみる』の項を参照してください。
ところで、メッセージを渡すにはPostMessage関数によるメッセージのポストとSendMessage関数によるメッセージの送信がありますが、SendMessage関数は、メッセージ処理が完了するまでスレッドを止めてしまうという特性上、デッドロック(スレッド同士で互いのスレッドの作業が終わるのを待ってしまう結果、プログラムそのものが停止してしまうこと)が発生するプログラムを作ってしまう可能性が高くなってしまいます。これは、hsgetmsg.dllを使ってメッセージ取得を行なっている限りはありえないことですが、メッセージの割り込み処理機能を使った場合には十分にありえることです。したがって、ここでは PostMessage関数を使ってメッセージを送ることにします。
あえてメッセージ処理を待つ必要がある場合にはSendMessage関数を使うことになりますが、デッドロックを起こさないように十分注意してプログラミングを行なう必要があります。(hsgetmsg.dllでは意味がありませんが。)
以上のことから、手順はだいたい以下のようになるかと思います。
今回使用するAPI関数は以前に説明したものばかりですので、詳しい説明は要らないでしょう。
処理を各部分に分けてみていくことにします。
まずは以下の処理をするためのスクリプトです。
#include "llmod.as" #include "hsgetmsg.as" #include "apierr.as" ; エラーコード取得モジュール #define MYWM_DBEXE 0x0410 ; アプリケーション固有メッセージ mapobjname = "urawaza_mapobj" ; ファイルマッピングオブジェクトの名前 ; ファイルマッピングオブジェクトの作成 pm.0 = -1 ; 0xFFFFFFFF pm.1 = 0 ; NULL pm.2 = 4 ; PAGE_READWRITE pm.3 = 0 pm.4 = 1024 ; ファイルマッピングオブジェクトのサイズ getptr pm.5, mapobjname ; オブジェクト名のアドレス dllproc "CreateFileMappingA", pm, 6, D_KERNEL hmapobj = stat ; オブジェクトのハンドル ; オブジェクトが作成されていたかどうかの判別 geterrcode ; GetLastError関数によるエラーコード取得 if stat == 183 { ; ERROR_ALREADY_EXISTS goto *lb_exist ; すでに同じ名前のマッピングオブジェクトが ; 存在する場合 } else { goto *lb_new ; マッピングオブジェクトが存在しない場合 }
アプリケーション固有のメッセージコードとし0x0410をMYWM_DBEXEという定数名で定義してあります。これは、あとでメッセージをやり取りする際に使用するものです。ここに指定するメッセージ別の値でもいいですが、値は0x0400以上のものにする必要があります。それ以外のものを使うと、動作がおかしくなる可能性があります。
ファイルマッピングオブジェクトは、同じ名前を指定すればどのプロセスからでもそのオブジェクトを操作できてしまいます。そのため、これらの名前はアプリケーション固有のものにして、他のアプリケーションが使うオブジェクトと重ならないようにしましょう。
マッピングオブジェクトが作成されていなかった場合(初回起動の場合)の処理です。
*lb_new onexit *lb_on_exit ; ウィンドウのサブクラス化 set_subclass hwnd = stat ; ウィンドウハンドル set_message MYWM_DBEXE ; メッセージ設定(固有メッセージ) ; ビューのマッピング pm.0 = hmapobj ;マッピングオブジェクトハンドル pm.1 = 2 ; FILE_MAP_WRITE(読み書きアクセス) pm.2 = 0 ; オフセット(上位) pm.3 = 0 ; オフセット(下位) pm.4 = 0 ; サイズ(オブジェクト全体を指定) dllproc "MapViewOfFile", pm, 5, D_KERNEL lpdata = stat ; データのアドレス ; ウィンドウハンドルを共有メモリに格納 ll_poke4 hwnd, lpdata ; ビューをマッピング解除 dllproc "UnmapViewOfFile", lpdata, 1, D_KERNEL dup msg, msgval.1 dup wprm, msgval.2 dup lprm, msgval.3 *mainloop get_message if msgval { if msg == MYWM_DBEXE { ; メッセージを受け取ったとき gsel 0, 2 dialog "起動されました" gsel 0, 1 } } else { wait 10 } goto *mainloop *lb_on_exit ; ファイルマッピングオブジェクトのクローズ(終了時) dllproc "CloseHandle", hmapobj, 1, D_KERNEL end
マッピングオブジェクトが作成されていた場合(2回目以降の起動の場合)の処理です。
*lb_exist ; ビューのマッピング pm.0 = hmapobj ; マッピングオブジェクトハンドル pm.1 = 4 ; FILE_MAP_READ(読み込み専用アクセス) pm.2 = 0 ; オフセット(上位) pm.3 = 0 ; オフセット(下位) pm.4 = 0 ; サイズ(オブジェクト全体を指定) dllproc "MapViewOfFile", pm, 5, D_KERNEL lpdata = stat ; データのアドレス ; 共有メモリからウィンドウハンドル取得 ll_peek4 hwnd, lpdata ; ビューをマッピング解除 dllproc "UnmapViewOfFile", lpdata, 1, D_KERNEL ; メッセージをポスト pm.0 = hwnd ; ウィンドウハンドル pm.1 = MYWM_DBEXE ; メッセージコード pm.2 = 0 ; wParam pm.3 = 0 ; lParam dllproc "PostMessageA", pm, 4, D_USER ; ファイルマッピングオブジェクトのクローズ dllproc "CloseHandle", hmapobj, 1, D_KERNEL end
上のスクリプトをいくつか同時に起動させてみましょう。2つ目、3つ目と起動されていくたびに、最初に起動されたプロセスのウィンドウに、起動されたことを示す文字列が表示されていくことと思います。
今回はメッセージを送るだけでしたが、メッセージを送る際には2つの4バイトデータをwParam、lParamパラメータとして送ることができますし、それ以上のサイズのデータ(例えばファイル名など)も共有メモリを使えば送ることができるでしょう。いろいろと各自で試してみてください。