プロセス間通信ではまる
統合環境のなかでいろいろな子ツールが動くようなことをしようとしています。これが安定してサクサクと軽快に動くようにしたいと思っています。
子ツールはEXEファイルになっていて、実行結果が親プロセスのメッセージウィンドウに表示されるというものです。
それで、Win32APIのマルチスレッドとかパイプ通信とかやっているのですが、なかなか勝手がつかめず、はまっています。
まず子プロセスを作って、子プロセスの中でprintfした場合に、標準出力を親プロセスに渡すようにしたいのですが、それをやるには、CreateProcess関数のStartupInfo構造体の中に細工すればよいということはわかりました。
親プロセスはCreateProcessに先立ってCreatePipeで匿名パイプを作っておき、STARTUPINFO構造体のhStdOutputにその匿名パイプの入力側を入れます。
つまり、
HANDLE hRead,hWrite;
CreatePipe(&hRead, &hWrite, &sa, 0);
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo,sizeof(startupInfo));
startupInfo.cb=sizeof(startupInfo);
startupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
startupInfo.hStdOutput = hWrite;
startupInfo.hStdError = hWrite;
CreateProcess(NULL,cmdFileName,NULL,NULL,TRUE,
NORMAL_PRIORITY_CLASS,NULL,NULL
,&startupInfo,&processInfo
これで、子プロセスがprintfしたものが次々と匿名パイプに入ってきます。親プロセスではhReadからどんどん読み出して、自分のメッセージウィンドウなどに表示してやればよいのです。
しかし、Windowsでは、パイプの監視が一筋縄ではいかないということがわかってきました。
有名なAPI関数にWaitForSingleObjectというのがありますが、これはパイプは監視できないようです。
そのため、ループ中で常にパイプの監視をしてやらなければならないのですが、ずっと監視しているとCPUの使用率が上がります。要はパイプに何かのデータが来たらイベントを発生させたいのですが、イベントを発生させるにしても、やはり常にパイプを監視していなければなりません。ここはマルチスレッド化しても同じです。
さて、Borland C++ Builderを使っているときにはメインのスレッド(ボタン押したときの処理などをするスレッド)は止めるとよくないので長時間止めるときには、Application->ProcessMessage()を呼び出さないと、画面が固まったようになってしまいます。
メインスレッドでWaitForSingleObjectの関数を使っても画面が固まるようです。子プロセス起動とかするとさらにややこしくなります。子プロセスの監視を子スレッドにやらせた場合、ButtonやMemoなどのコンポーネントに文字を表示させたい場合、親スレッドが停止していると更新されないようです。
つまり、親スレッドは常に動いてApplication->ProcessMessage()をしていなければならないようなのです。子スレッドの終了監視もしなければならない。CPU使用率を下げるために親スレッドを止めると、Memoなどへの表示更新も遅くなる。
これを回避するには、メインのスレッドで動く関数は子スレッドを起動したら監視などせずにすぐに制御を返せばよいのですが、これでは子プロセスの終了待ちができません。子プロセスが終了を以って何らかの処理をするには、子プロセスが終了したときに何かのイベント(Borlandのイベント)を起こしてやらなければなりません。
この処理では1つの関数ではできず、子スレッドを起動するまでと、子スレッド終了以降とにわけで、2つ以上の関数にしなければなりません。これは面倒。
つまり、以下3つのことが同時に成り立たないようです。
・表示が速やかに更新されること
・CPU使用率を下げること
・コードを簡単にすること
さて、起動した子プロセスと通信するには名前付きパイプなどの手段を使いますが、やはり名前付きパイプはWaitForSingleObjectで待てないので、パイプと一緒にイベントも作ってやらねばなりません。
つまり、パイプに何かを書いたらSetEventでイベントを発生させて、それを知らせてやらねばなりません。
これらはWindows APIのCreateNamedPipeやCreateEventを使いますが、CreateNamedPipe関数もCreateEvent関数も成功するとハンドルを返します。
しかし、CreateEvent関数は失敗するとNULLを返し、
CreateNamedPipe関数はINVALID_HANDLE_VALUE(=0xFFFFFFFF)を返します。
なんてややこしい。
OpenEventは、どうやら親スレッドが作ったEventを子スレッドでも使う場合に用いる関数のようです。親子スレッドでイベントを共有する場合、面倒だからとCreateEventだけで済ませてHANDLEだけを渡すと、なんだか変なことになるようです。
全く別個に起動したプログラム同士でイベントを共有するには、同じ名前でCreateEventをすればよいようです。果たしてそれでよいのかまだ不明。ちなみに、名前を同じにしてもOpenEventでは開けませんでした。
このあたりの仕組みがだんだん理解できてきました。
最近のコメント