プログラミング工房 > HSP > サンプルスクリプト > 

名なしパイプと独自メッセージによるプロセス間通信(エディタと外部プログラムの連携)

(2006/6/20更新)

 このサンプルは、名なしパイプと独自のウィンドウメッセージを利用し、2つのアプリケーション間でテキストデータのやりとりを行う機能を記述したスクリプトです。動作確認はHSP3.1 beta3 / WinXP Pro SP2で行っています。

 下のリンクからダウンロードできるアーカイブには、このページで解説する2つのスクリプトとその動作に必要な1つのスクリプトが入っています。(動作テストを行うには少なくともこのうちの"tool.hsp"をコンパイルして実行ファイルにしておく必要があります。標準エディタの「実行ファイル自動作成」メニューで作成してください)

 >> サンプルをダウンロード


2つのスクリプトの関係と全体の処理の流れ

 サンプルスクリプトの"editor.hsp"と"tool.hsp"は、それぞれランチャー機能付きのテキストエディタと、エディタから起動されてエディタとプロセス間通信をしながら小さな仕事を行うユーティリティを模して書かれています(サンプルなので構造はごくシンプルです)。ちょうどテキストエディタのTeraPadとTeraPadツールのような関係です。ただこのサンプルはTeraPadの仕組みを調べて書いたわけではありません。

 2つのソフトが行う処理の流れは以下のようになります。

 editor.exe起動(editor.hspを読み込みmesboxに表示。tool.exe起動用のbuttonを表示)(ユーザが操作)
  ↓
 mesbox内のテキストの一部または全体を選択(ユーザが操作)
  ↓
 editor.exeのウィンドウ上のボタンをクリックしてtool.exeを起動(ユーザが操作)
  ↓
 tool.exeがeditor.exeのウィンドウを検知したあと、editor.exeに対して
 mesbox内の選択テキストを送信するように要求する
  ↓
 editor.exeがtool.exeにデータを送る
  ↓
 tool.exeが受け取ったテキストを加工する
  ↓
 tool.exeがeditor.exeに対してデータを受け取る処理に入るように要求する
  ↓
 tool.exeが加工済みのテキストをeditor.exeに送り返す
  ↓
 editor.exeが受け取ったテキストをmesbox内の選択テキストと置き換える
  ↓
 終了

 "tool.exe"は"editor.exe"から起動され自分の仕事を完了すると自動的に終了します。


editor.hsp

 >> スクリプトを見る


スクリプトの骨組み

 "editor.hsp"の骨組みは、初期化処理として必要なGUIを作成・表示した後、ユーザの操作や"tool.exe"からのメッセージ(通信の要求)を待つための【【メインループ】】(無限ループ)に入り、それらのイベントが起こった時点で対応したサブルーチンに飛び処理を行い、また【【メインループ】】に戻り次のイベント待つ、という形になっています。


"tool.exe"との通信1(選択テキスト送信)

 "tool.exe"との連携処理はユーザがウィンドウ上の"外部プログラム起動"ボタンをクリックして"tool.exe"を起動することで始まりますが、"tool.exe"が起動してから終了するまでは"tool.exe"からのウィンドウメッセージがきっかけですべての処理が進みます。

 (HSPではメッセージなどによる割り込みのチェックは、"stop"、"wait"、"await"のいずれかの命令の実行時に行われます。つまりこのプログラムでボタンが押されたときや、ウィンドウメッセージが来たときのサブルーチンジャンプは【【メインループ】】内の"await 1"のタイミングで行われ、サブルーチンの実行が終了すると同じ場所に戻ってきて【【メインループ】】が継続されます)

 "tool.exe"は起動するとまず"EDT_WM_GETSELTEXT"というメッセージをウィンドウに送ってきます。このメッセージはサンプルプログラム専用に定義したもので(関係ないプログラムに送信すると意図しない反応をされるか、もしくは無視されます)、mesbox内の選択テキストの送信要求を意味しています。

 "editor.hsp(.exe)"はこのメッセージを受け取ると、【【割り込み実行サブルーチン】】の"*OnToolMsg"ラベルに飛び、メッセージ自体の値とWPARAM、LPARAMの値を変数に記録して一度【【メインループ】】に戻ります。そして次に"switch"ブロック内で変数"_toolmsg"に"EDT_WM_GETSELTEXT"がセットされているのを見つけ、【【選択テキスト送信サブルーチン】】の"*SendSelText"に飛びます。

 【【選択テキスト送信サブルーチン】】ではmesbox内の選択テキストの取得と"tool.exe"への送信を行います。ただし最初に"EM_GETSEL"メッセージを使って選択テキストの長さを調べ、テキストがまったく選択されていない場合はなにもせずサブルーチンを抜けて【【メインループ】】に戻ります。このサブルーチンでなにもしない場合でも、変数"_hpipe"に対して"CloseHandle" APIを実行してメッセージのWPARAM経由で受け取っていた通信用のパイプハンドルを閉じる処理と、変数"_toolmsg"のクリアは必ず行う必要があります。"CloseHandle"を実行しないと"tool.exe"は通信の終了を検知できず動作がストップしたままプロセスが存在し続けることになってしまいますし、変数"_toolmsg"をクリアしないと、必要がないのにもう一度このサブルーチンに飛んできてしまうことになります。

 mesboxテキストの一部またはすべてが選択されていた場合はそれを取得します。取得は一番簡単な"mesbox"に"WM_COPY"メッセージを送ってクリップボードを経由する方法で行います。クリップボードからテキストを取得する際、"editor.hsp"でインクルードしているモジュール"gm_clipboard_text.hsp"内の"getcbtext"命令を使用します。

 テキストが取得できたらそれを"tool.exe"が作成した通信用の名なしパイプに書き込みます。書き込みはあらかじめ必要な回数を計算しておいてから、変数"_hpipe"に格納されているハンドルに対して"WriteFile" APIを繰り返し実行して行います。"WriteFile"を実行するには書き込むサイズを指定しなければならないため、送信するテキストが何回分の分量なのか確認する必要があるのです。すべてのテキストの送信が終了したら"CloseHandle" APIでパイプのハンドルを閉じ、変数"_toolmsg"をクリアして【【メインループ】】に戻ります。ここで一度"tool.exe"との通信は終了します。


"tool.exe"との通信2(加工済みテキストの受信)

 最初の通信でこちらが送ったテキストを受け取った"tool.exe"は、あらかじめ決められた形でそれを加工した後、今度はテキストを送り返すために"EDT_WM_SETSELTEXT"メッセージを送ってきます。

 それに対してこちらでは最初の通信時と同様にメッセージの内容を記録・識別し、今度は【【テキスト受信・選択テキスト置換サブルーチン】】の"*ReplaceSelText"ラベルに飛びます。

 【【テキスト受信・選択テキスト置換サブルーチン】】では先ほどとは逆にパイプ経由でテキストを受信し、それをmesbox内の選択テキストと置き換えます。サブルーチンに入った時点で変数"_hpipe"には"tool.exe"が新しく作成した名なしパイプの読み取り側ハンドルが格納されており、これを引数に使用して"ReadFile" APIを実行することでデータを受信することができます。このとき固定サイズのバッファを使い戻り値の読み取りサイズが0になるまで"ReadFile"を繰り返す必要があります。("tool.exe"はこの通信でテキストを送り終わると自動的に終了します)

 受信が終了したら最初の通信時と同様にパイプハンドルのクローズと変数"_toolmsg"のクリアを行い、最後にmesboxに"EM_REPLACESEL"メッセージを送り選択テキストを置換してサブルーチンを終了します。

 以上で"tool.exe"との連携処理は完了です。


tool.hsp

 >> スクリプトを見る


スクリプトの骨組み

 "tool.hsp"の骨組みは、下準備・テキストの受信・受信したテキストの加工・テキストの送信、の各処理を順番に実行し自動的に終了する単純なものです。

 "tool.hsp"は実行ファイル("tool.exe")にしておかないと起動されませんので注意して下さい。


下準備(自分と通信相手のプロセスハンドルの取得)

 "editor.hsp"の説明に書いたように、"tool.exe"は通信のために名なしパイプを作成してそのハンドルを"editor.hsp(.exe)"に渡しますが、"tool.exe"にとって"editor.hsp(.exe)"は子プロセスではないため、ハンドルをそのまま渡しても"editor.hsp(.exe)"には使うことができません。自分が作ったパイプのハンドルを子プロセスでない他のプロセスにも使えるようにするには、"DuplicateHandle"というAPIでハンドルを「複製」してそのハンドルを渡す必要があります。そしてこの"DuplicateHandle"の実行には、引数として自分とハンドルを使わせる相手のプロセスハンドルが必要なので、それらを最初に取得します。

 まず通信相手のプロセスハンドルを取得する方法ですが、今回のサンプルの場合"tool.exe"の起動直後には"editor.hsp(.exe)"のウィンドウが最前面にあると仮定して、「フォアグラウンドウィンドウのハンドルを取得」→「そのウィンドウハンドルに関連づけられたプロセスIDを取得」→「そのプロセスハンドルを取得」という手順をとっています。

 それぞれの作業は"GetForegroundWindow"、"GetWindowThreadProcessId"、"OpenProcess"の3つのAPIによって行いますが、"OpenProcess" APIの第1引数には必ず0x40を指定してプロセスハンドルをDuplicateHandle APIで使えるようにする必要があります。また取得したプロセスハンドルは後で不要になった時点で"CloseHandle" APIを使って必ず閉じるようにします。(スクリプト上で明示的に閉じなくても結局"tool.exe"の終了時にWindowsが閉じてくれるのですが、自分で閉じる癖をつけておいた方が無難です)

 自分のプロセスハンドルの取得は"GetCurrentProcess" APIを引数なしで実行して戻り値を取得すればOKです。このAPIで取得したハンドルは擬似ハンドルと呼ばれるもので、"CloseHandle"で閉じる必要はありません。


"editor.hsp(.exe)"との通信1(mesboxの選択テキストを受信)

 この部分のスクリプトは主に「名なしパイプを作成」、「"editor.hsp(.exe)"に渡すパイプハンドル(書き込み側)を複製」、「通信の準備を促すメッセージをパイプハンドルと共にエディタに送信」、「パイプを通じてデータを受け取る」の4つのパートからなっています。

 まず2つのアプリケーションの間でデータの受け渡しに使う名なしパイプを作成します。この処理は後でもう一度実行するのでサブルーチンにしてあります。名なしパイプを作成するには"CreatePipe" APIを使います。第1、第2引数には読み出し側・書き込み側の各ハンドルを受け取るための変数のポインタを指定して下さい。第3引数はSECURITY_ATTRIBUTES構造体という種類のデータへのポインタを指定します。構造体の各メンバの意味は、構造体のサイズ、セキュリティ・ディスクリプタへのポインタ、ハンドルを継承させるかどうかの真偽値です。セキュリティ・ディスクリプタへのポインタはパイプ(ハンドル)にアクセスするのが作成したユーザのみの場合、0でかまいません。またハンドルの継承については、ハンドルを子プロセス以外に渡す場合は関係ないので、0を指定します。第4引数にはパイプのために確保されるバッファサイズを指定します。0の場合デフォルトの値が使われます。

 パイプの作成に成功したら、"editor.hsp(.exe)"に渡す書き込み側のパイプハンドルを"DuplicateHandle" APIで複製します。先頭4つの引数には、下準備で取得したプロセスハンドルと作成したパイプのハンドルを指定します。第5引数は複製したハンドルのアクセス権に関するものなのですが、オリジナルのハンドルと同じことができればいい場合、0でかまいません。第6引数には複製したハンドルを継承可能にするどうかを真偽値で指定します。今回の場合不要なのでFALSE(0)にします。最後の引数はAPIの動作オプションを指定するためのもので、0でも特に問題ないのですが、オリジナルのハンドルを自動で閉じてもらうために"DUPLICATE_CLOSE_SOURCE"(0x01)を指定し、さらに念のために第5引数を無視する動作オプション"DUPLICATE_SAME_ACCESS"(0x02)を加えて指定しています。

 次は、複製したハンドルを通信の準備を促すメッセージ(テキストを送信してもらうために"EDT_WM_GETSELTEXT"を指定)にくっつけて、"sendmsg"命令で"editor.hsp(.exe)"に送ります。必要なウィンドウハンドルは下準備で取得したものを使います。複製したハンドルはこれ以後使うことはないのでここで閉じてしまいます。

 あとはパイプ経由でテキストを受信し、パイプの読み取り側ハンドルを閉じて1度目の通信を終了します。処理の内容は"editor.hsp(.exe)"の受信処理部分とまったく同じです。


受信したテキストを加工

 "editor.hsp(.exe)"から受け取ったテキストをnote系の命令を利用して加工します。各行頭に"// "を付加するだけの処理です。テキストのサイズが0の場合は即、終了処理に飛びます。


"editor.hsp(.exe)"との通信2(加工済みテキストを送信)

 データの受信処理が送信に代わった以外は通信1とほぼ同じです。"DuplicateHandle"するときに書き込み側のパイプハンドルではなく読み出し側を複製しています。送信処理は"editor.hsp(.exe)"が行っているものとまったく同じです。


後始末

 最後に"editor.hsp(.exe)"のプロセスハンドルを閉じ、"end"命令によって自動的に終了します。