前回は、メモリマップトファイルを用いた共有メモリによってプロセス間のデータ共有を行う方法を説明しましたが、今回はウィンドウメッセージを用いた方法を説明していきましょう。原則:別プロセスにポインタは渡せない前回にも説明したように、各プロセスは独立した仮想アドレス空間を持っているため、あるプロセスAでデータが格納されているメモリブロックのアドレスを別のプロセスBに渡したとしても、そのアドレスはプロセスBでは無効なものとなっています。メモリの共有には、メモリマップトファイルのように特殊な方法を使用しなければなりません。これはウィンドウメッセージを送信またはポストする際にも言えることで、パラメータにメモリアドレスを含む種類のウィンドウメッセージは、原則として、プロセスを超えて送ることができないようになっています。プロセスを超えてメッセージを送る場合には、wParamパラメータやlParamパラメータにメモリアドレスが含まれていてはいけないということです。メモリアドレスを含むメッセージを送ってしまうと、送信先では、自分のプロセスの、実際にはデータが存在しないアドレス空間にアクセスしようとして、強制終了してしまう可能性があります。メッセージを送信した方ではなく、受け取った方のプロセスが終了してしまうという事態に陥りかねないのです。WM_APPWindowsでは、システムが定義しているさまざまなメッセージコードのほかに、アプリケーションが自由に意味を定義して使用することができるメッセージコードというものを準備しています。具体的には、以下の範囲で目的別に使用できるようにしているのです。予約されていますこれらのうち、0x0400~0xBFFFの範囲で自由にメッセージコードの意味を定義して使用することができます。(Xは0以上の整数)いう形で定義できるようになっています。0x8000ここで、WM_USERの範囲とWM_APPの範囲に分けられているのは、WM_USERの範囲のメッセージコードはWindowsのコントロール(ボタンやエディットコントロールなど)が使用しているメッセージコードと重なる部分があり、WM_APPの範囲のコードではそのような競合がないということです。今回の場合はコントロールに送ったりするわけではないので、これらの違いをあまり意識する必要はないと思います。95/98/Meでは、ダイアログ制御メッセージとの衝突を避けるためにWM_USER+0x0100(すなわち0x0500)より大きい値を定義しなければならないことになっています。ダイアログ制御メッセージはダイアログ用ウィンドウプロシージャで使用されるだけで、通常のウィンドウでは使用されないと考えられますが、すべての環境でうまく動作させるため、とりあえずそのような値を使用した方がよいでしょう。これらのメッセージコードを使用してメッセージのやり取りをする際に、wParamメッセージやlParamメッセージに任意の値を指定することができます。したがって、一回のメッセージ送信で、同時に8バイトまでのデータを送信することができるということです。8バイトより大きいデータを送る場合に、複数回に分けてメッセージを送るという手段をとっているソフトウェアもあるようです。それぞれ送信側・受信側の処理は以下のようになります。(ただし、送信先ウィンドウハンドルの取得を行う処理をしていないので、このスクリプトのままでは動作しません。送信先ウィンドウの取得については後述。WM_GETTEXTWindowsには、メモリアドレスを渡すにもかかわらず、プロセスを超えて渡すことができるメッセージがいくつか存在します。そのうちの代表的なものはWM_SETTEXTおよびWM_GETTEXTです。これらのメッセージは、ウィンドウに関連付けられている文字列の設定や取得を行うメッセージです。タイトルバーがついている通常のウィンドウに送信すると、ウィンドウのタイトル文字列が対象となります。エディットボックスやボタンなどのなどのコントロールに送信すると、それらのコントロールに表示されている文字列が対象となります。これらのメッセージは、16ビット時代からの互換性のために、プロセスを超えて送られた場合でも上手く動作するように実装されています。したがって、プロセス間でやり取りする情報が文字列だけであるならば、ユーザーからは見えないエディットコントロールを準備して、それを介してプロセス間通信を行うということも可能なのです。任意サイズのバイナリデータを渡す:WM_COPYDATAこれとは別に、ウィンドウメッセージを介してプロセス間通信を行うことを目的としているメッセージがあります。それが、WM_COPYDATAメッセージです。WM_COPYDATAメッセージは任意のサイズのバイナリデータを別のウィンドウへ送信する機能を持ちます。送信先ウィンドウが送信元とは異なるプロセスに属していても動作するようになっています。実際にはこのメッセージは、前回のメモリマップトファイルを介した共有メモリによる通信を内部で行っています。私たちプログラマからはそれが見えないようになっているだけです。そのおかげで私たちは、共有メモリなどの概念を考えることなく、このメッセージを使って簡単にプロセス間通信を行うことができるのです。このメッセージの使用方法としては、送信側でSendMessage関数を使ってWM_COPYDATAメッセージを送信し、受信側のウィンドウプロシージャで同じくWM_COPYDATAメッセージに対する処理を行うことになります。《送信側の処理》WM_COPYDATAメッセージのメッセージコードは0x004Aです。wParamパラメータには、データ送信側(自分自身のウィンドウハンドル)を指定する必要があります。lParamパラメータには、渡されるデータに関する情報を格納したCOPYDATASTRUCT構造体のアドレスを指定します。*PCOPYDATASTRUCT;dwDataメンバでは任意の整数値を1つ渡すことができます。lpDataには、渡されるバイナリデータが格納されているメモリブロックのアドレスを指定します。そのデータサイズをバイト単位でcbDataメンバに指定しておく必要があります。dwDataメンバには、lpDataが指すメモリブロックに格納されているデータがどんな種類のデータであるかを識別するIDを格納しておくと便利です。送信側はこのメッセージをSendMessage関数を使って送信しなければいけません。PostMessage関数によりメッセージをポストすることはできません。varptr(cds)今回のようにウィンドウメッセージを介してデータ通信を行う場合には、何らかの方法で送信先のウィンドウハンドルを取得しておかなければなりません。これについては後で述べます。次に受け取る側の処理についてです。送信側がWM_COPYDATAメッセージを送信すると、受信側では同じWM_COPYDATAメッセージを受け取ることになります。したがって、ウィンドウプロシージャでデータ受信処理を行うことになります。HSPの場合にはWM_COPYDATAメッセージを受け取ったときに割り込み処理を行うように設定しておき、割り込みが発生したところで受信処理を行うということです。WM_COPYDATAメッセージを受け取ったときのwParamパラメータやlParamパラメータについては送信側と同様、wParamには送信側のウィンドウハンドルが、lParamパラメータにはCOPYDATASTRUCT構造体のアドレスが指定されています。内部での共有メモリ作成やアドレス変換により、送信側とまったく同じアドレスになっているということはありませんが。受信側では、COPYDATASTRUCT構造体のdwDataメンバから、どんなデータが送られてきたかを判断して(1種類のデータしかやり取りしない場合は必要ないでしょうが)、lpDataメンバのメモリアドレスが指すメモリブロックに対しての処理を行うことになります。ここで注意しておくべきことは、受け取ったデータのメモリブロックは、メッセージ処理を行っている間のみ有効であって、同じアドレスに後でアクセスすることはできないということです。HSPの場合には、割り込み処理が終了した時点でこれらのアドレスは無効になるため、あとでこれらのアドレスからデータを読み取ることはできないということになります。したがって、後でデータが必要になる場合には、別の変数にデータを格納しておく必要があります。0通信相手のウィンドウを探すには実際にウィンドウメッセージを送信するためには、送信先のウィンドウのハンドルを取得しなければなりません。ハンドルを取得する方法はいくつか考えられます。親プロセスからコマンドパラメータとして渡す多くの場合、データ共有が必要になるのは、親プロセスがサブプロセスを起動してデータを引き継がせる場合でしょう。ウィンドウハンドルはどのプロセスからも同じ値で参照されるため、このようにプロセス間通信を行う相手プロセスをサブプロセスとして新しく起動する場合には、コマンドラインパラメータとして親プロセスのウィンドウハンドルを渡すことができます。この場合には、サブプロセスが(少なくとも最初の)メッセージ送信側となります。ウィンドウタイトルから検索するすでに起動しているプロセスのウィンドウハンドルを得る方法として、ウィンドウタイトルから取得する方法があります。これには、user32.dllが提供するFindWindow関数を使います。ウィンドウタイトル.);1つ目のpClassNameパラメータにはウィンドウクラス名を表す文字列のアドレスを指定します。指定することができます。HSP3の場合、ウィンドウクラス名は「hspwnd0」なので、これを指定しておいた方がよいでしょう。2つ目のpWindowNameパラメータにはウィンドウタイトルを表す文字列のアドレスを指定します。指定することができます。クラス名「hspwnd0」だけを指定してウィンドウを検索すると、HSP3で作成されたすべてのアプリケーションのすべてのウィンドウ(ID1以降も含めて)がヒット対象となるので、同時にタイトル文字列も指定しておく必要があります。そこで上記のサンプルでは、送信先ウィンドウが見つからなかったときにエラーが起こったことを知らせるメッセージボックスを表示しています。この方法でのウィンドウハンドル取得では問題が発生することがあります。FindWindow関数はウィンドウタイトルが指定されたものと完全に一致するものに対してヒットさせるので、何らかの理由でウィンドウタイトルが変えられていた場合にはヒットしなくなるということです。ソフトウェアの中には、他のソフトウェアのウィンドウタイトルを変えてしまうものが存在するため、注意が必要です。ひとつの問題として、同じタイトルをもったウィンドウが他にも存在する可能性があるということです。アプリケーション固有のウィンドウタイトルにしておけばほとんど問題ないと思いますが、もし偶然にも同じタイトル文字列を持つウィンドウが存在した場合には、そちらが取得されてしまう可能性があります。全ウィンドウにコンタクトしてみる:ブロードキャスト別のアプローチとして、「とりあえず存在するウィンドウすべてにメッセージを送信してみて、相手側からの反応を待つ」というものがあります。指定することによって、デスクトップ上のすべてのトップレベルウィンドウにメッセージを送ることができるようになっています(ブロードキャストメッセージ)。これを使って全ウィンドウにメッセージを送ってみるということです。ブロードキャストメッセージを使用するには注意が必要で、もしこのメッセージをSendMessage関数で送信した場合、システム上のどれか1つのウィンドウでもハングアップ(フリーズ)していると、こちらのプログラムもまたハングアップしてしまう可能性があるということです。PostMessage関数を使う場合にはその危険性はなくなりますが、同期(タイミング)のとり方などが少し難しくなる可能性があります。メッセージの送信において、SendMessage関数の代わりにSendMessageTimeout関数を使うことによって、メッセージ処理の最大待ち時間を指定することができるようになっています。この関数では、メッセージ送信先がハングアップ状態であっても、待ち時間が過ぎると制御が戻るようになっています。ブロードキャストメッセージを使用する場合にはSendMessageTimeout関数を使用するほうがよいでしょう。ブロードキャストメッセージを送信する場合、上記のようなWM_APP+X(あるいはWM_USER+X)のような値を使用してはいけません。他のアプリケーションは、これらのメッセージコードを別の意味で使用している可能性があるためです。ブロードキャストメッセージを送信する際に使用するメッセージコードは、システム上で一意である(それ以外の別の意味を持っていない)必要があります。そのようなメッセージコードは、user32.dllのRegisterWindowMessageを使って取得することができます。メッセージ文字列.);この関数は、システム上で一意のメッセージを登録するためのもので、固有の名前を渡すことにより、その名前に対応したメッセージコードを取得することができます。どのプロセスからでも、同じ名前を渡すことによって、同じメッセージコードを受け取ることができるようになっています。この関数で取得されるメッセージは文字列メッセージなどと呼ばれます。以下のスクリプトは、ブロードキャストメッセージを使って相手のウィンドウを探す簡単なサンプルです。hwndreturnまず、送信側が「HSP_Broadcast_Test」文字列メッセージ(RegisterWindowMessage関数で登録したもの)をブロードキャストメッセージとして全ウィンドウに送信しています。受信側がこれを受け取ると、今度は返信メッセージ(ここではMYWM_RECEIVEと定義)を返しています。どちらのメッセージでもlParamパラメータに自分自身のウィンドウハンドルを渡すことによって、ウィンドウハンドルを知らせるようにしています。このハンドルを保存しておけば、これ以降でウィンドウメッセージによる通信を続けることができるようになります。ブロードキャストメッセージは若干の負担がかかるため、これを使用するのは最初の1回のみにとどめておいた方が無難です。