トップ回答者
パイプ処理の制約?(VC++/cr)

質問
-
以下のコード で親プログラムから呼ばれたパイプ処理をしている。ある量を超えた時点からWaitForSingleObjectでハングするようになりINFINITEに値(10000,20000,30000)を与えると途中で処理が打ちきらられてしまうが正常に処理は行う。
int procCmd::syncProcCmd(LPWSTR cmd, BYTE ** cmdResult, int &stdLen, BYTE ** stdError, int &stdELen){
HANDLE hPipe[2]; //実行する外部コマンドの出力を読み込むためのパイプ
HANDLE hPipeE[2];
SECURITY_ATTRIBUTES secAtt; //セキュリティ情報(パイプの作成に必要)
STARTUPINFO si; //起動条件
PROCESS_INFORMATION pi; //プロセス情報
BOOL bChk;
LPVOID lpMsgBuf;
//---------------
//パイプの作成
//---------------
secAtt.nLength = sizeof(SECURITY_ATTRIBUTES);
secAtt.lpSecurityDescriptor = NULL;
secAtt.bInheritHandle = TRUE;
CreatePipe(&hPipe[0],&hPipe[1],&secAtt,0);
CreatePipe(&hPipeE[0],&hPipeE[1],&secAtt,0);
//---------------
//起動条件の設定
//---------------
memset(&si,NULL,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USEFILLATTRIBUTE|STARTF_USECOUNTCHARS|STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = hPipe[1];
si.hStdError = hPipeE[1];
ZeroMemory( &pi, sizeof( PROCESS_INFORMATION ) );
//基本設定終了
//--------------
//プロセス起動
//-------------
bChk = CreateProcess (NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
if (bChk != TRUE) {FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL );MessageBox( NULL, (LPCTSTR)lpMsgBuf, L"外部コマンド起動失敗", MB_OK |MB_ICONINFORMATION );
LocalFree( lpMsgBuf );
return NULL;
}
DWORD sig = WaitForSingleObject(pi.hProcess, INFINITE); // 終了まで待つ実際には成功しているものはSJISで3kBくらいで駄目なものは7KBくらい。perlでwebから読み込んで加工した結果をsjisで受け取る単純なもの。
何か特別な処理を組み込まないと駄目とか制約があるのでしょうか?他の例でもあまり大きなサイズの受け渡しはしないので問題にはなっていないのですが。
回答
-
CreatePipe はデータがセットされたかどうかを待機することができます。ですので、WaitForMultipleObjects などで、パイプ2個&プロセスをずっと待ち続けることで、パイプにデータが来た場合(何かしら書き込みがあった場合)、プロセスが終了した場合を待ち合わせることができます。
//DWORD sig = WaitForSingleObject(pi.hProcess, INFINITE); // 終了まで待つ HANDLE waitHandles[3]; waitHandles[0] = hPipe[1]; waitHandles[1] = hPipeE[1]; waitHandles[2] = pi.hProcess; DWORD sig; do{ sig = WaitForMultipleObjects( 3, waitHandles, FALSE, INFINITE ); switch( sig ){ case WAIT_OBJECT_0+0: // hPipe[1] に書き込みあり ReadFile( hPipe[1], ... ); break; case WAIT_OBJECT_0+1: // hPipeE[1] に書き込みあり ... break; } }while( sig != WAIT_OBJECT_0+2 );
こんな感じにしてやることで、何かあればそれをという形にできます。
書き込まれたデータに関しての処理については特に記述がないので何もフォローしていませんが、パイプはこういう形で待機させることができるため、うまく作ればプロセス間通信(あるいは、スレッド間通信)などでも役に立つ場合があります。
詳しくはMSDNライブラリをご覧ください。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/- 回答としてマーク アレックス 2011年2月14日 19:13
-
すいません。ざっとリファレンスを見た限りでは無名パイプのハンドルには SYNCHRONIZE アクセス権(WaitForができる条件)がついてないみたいですね。なので私の提示した待機(どれかを待つ処理)は使えないみたいです(名前付きパイプのほうは見てないのでわかりません)。
さて、そうなると、必要があるまで待機という方法はとれませんので、処理を終了するまでひたすらループという形にするしかないと思います(最初に変更した適度にタイムアウトさせながら処理を行うという形)。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/- 回答としてマーク アレックス 2011年2月15日 17:38
すべての返信
-
書き込み先のパイプがいっぱいになって書き込みできなくなって待機しているのではないでしょうか?
システムデフォルトが何バイト確保しているのかわかりませんが、何もしなければいずれパンクします。そうなると書き込みできないために、書き出す側(子プロセス側)が待機状態に入ってしまいます。
親プロセスは何も考えずに待機していますので、この時点で、デッドロックとなり身動きが取れない状態になります。
なので、親プロセス側は、パイプを適宜読み取りつつ終了を待つというスタンスにする必要があると思います。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/ -
CreatePipe はデータがセットされたかどうかを待機することができます。ですので、WaitForMultipleObjects などで、パイプ2個&プロセスをずっと待ち続けることで、パイプにデータが来た場合(何かしら書き込みがあった場合)、プロセスが終了した場合を待ち合わせることができます。
//DWORD sig = WaitForSingleObject(pi.hProcess, INFINITE); // 終了まで待つ HANDLE waitHandles[3]; waitHandles[0] = hPipe[1]; waitHandles[1] = hPipeE[1]; waitHandles[2] = pi.hProcess; DWORD sig; do{ sig = WaitForMultipleObjects( 3, waitHandles, FALSE, INFINITE ); switch( sig ){ case WAIT_OBJECT_0+0: // hPipe[1] に書き込みあり ReadFile( hPipe[1], ... ); break; case WAIT_OBJECT_0+1: // hPipeE[1] に書き込みあり ... break; } }while( sig != WAIT_OBJECT_0+2 );
こんな感じにしてやることで、何かあればそれをという形にできます。
書き込まれたデータに関しての処理については特に記述がないので何もフォローしていませんが、パイプはこういう形で待機させることができるため、うまく作ればプロセス間通信(あるいは、スレッド間通信)などでも役に立つ場合があります。
詳しくはMSDNライブラリをご覧ください。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/- 回答としてマーク アレックス 2011年2月14日 19:13
-
補足。
このループは、メッセージを処理しないスレッド用に作ったものです(コンソールアプリやメインスレッドとは別のスレッドで動作する待機専用スレッドなど)。
もし、GUIアプリのメインスレッドで利用する場合はこの動作だとメッセージが処理されなくなるため、あまり好ましい状況とは言えなくなります。
その場合は MsgWaitForMultipleObjects などを使って待機するようにしてください。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/ -
早速試してみましたが、うまくいきませんでした。
以下のコードです。
//基本設定開始
HANDLE hPipe[2]; //実行する外部コマンドの出力を読み込むためのパイプ
HANDLE hPipeE[2];
SECURITY_ATTRIBUTES secAtt; //セキュリティ情報(パイプの作成に必要)
STARTUPINFO si; //起動条件
PROCESS_INFORMATION pi; //プロセス情報
BOOL bChk;
LPVOID lpMsgBuf;
//---------------
//パイプの作成
//---------------
secAtt.nLength = sizeof(SECURITY_ATTRIBUTES);
secAtt.lpSecurityDescriptor = NULL;
secAtt.bInheritHandle = TRUE;
CreatePipe(&hPipe[0],&hPipe[1],&secAtt,0);
CreatePipe(&hPipeE[0],&hPipeE[1],&secAtt,0);
//---------------
//起動条件の設定
//---------------
memset(&si,NULL,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USEFILLATTRIBUTE|STARTF_USECOUNTCHARS|STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.hStdOutput = hPipe[1];
si.hStdError = hPipeE[1];
ZeroMemory( &pi, sizeof( PROCESS_INFORMATION ) );
//基本設定終了
//--------------
if (buffSizeKB == NULL) {
MessageBox( NULL, L"buffSizeKB missing!", L"外部コマンド起動失敗", MB_OK | MB_ICONINFORMATION );
buffSizeKB = 32;
}
//プロセス起動
//-------------
bChk = CreateProcess (NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
// bChk = CreateProcess (NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
if (bChk != TRUE){
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL );
MessageBox( NULL, (LPCTSTR)lpMsgBuf, L"外部コマンド起動失敗", MB_OK | MB_ICONINFORMATION );
LocalFree( lpMsgBuf );
return NULL;
}
BYTE *tmp_cmdResult, *tmp_stdError;
int tmp_stdLen, tmp_stdELen, strSize, strSizeE, cnt;
strSize = 1024 * buffSizeKB;
strSizeE = 1024;
BYTE *tmp_str = new BYTE[strSize];
BYTE *tmp_strE = new BYTE[strSizeE];
ZeroMemory( tmp_str, strSize);
*stdELen = 0;
*stdLen = 0;
// DWORD sig = WaitForSingleObject(pi.hProcess, 100);
HANDLE waitHandles[3];
waitHandles[0] = hPipe[1];
waitHandles[1] = hPipeE[1];
waitHandles[2] = pi.hProcess;
DWORD sig;
do{
sig = WaitForMultipleObjects( 3, waitHandles, FALSE, INFINITE );
switch( sig ){
case WAIT_OBJECT_0+0: // hPipe[1] に書き込みあり
bChk = readFile2(hPipe[0], &tmp_cmdResult, &tmp_stdLen, L"StdOut 読込失敗");
if (tmp_stdLen > 0) {
strncat_s((char *)tmp_str, strSize, (char *)tmp_cmdResult, tmp_stdLen);
*stdLen += tmp_stdLen;
}
break;
case WAIT_OBJECT_0+1: // hPipeE[1] に書き込みあり
bChk = readFile2(hPipeE[0], &tmp_stdError, &tmp_stdELen, L"StdError 読込失敗");
if(tmp_stdELen > 0){
*stdELen = tmp_stdELen;
*stdError = new BYTE[*stdELen+1];
strncat_s((char *) tmp_strE, strSizeE, (char *)tmp_stdError, tmp_stdELen);
}
/* if (*stdLen > 0) {
MBMsgBox(3, L"StdError Message", *stdError, "::", *cmdResult);
}
else {
MBMsgBox(1, L"StdError Message", tmp_stdError);
}*/
break;
}
}while( sig != WAIT_OBJECT_0+2 );Do文を抜けられません。「case WAIT_OBJECT_0+1: // hPipeE[1] に書き込みあり」にSTDINの読み込みをセットすれば最初だけは読めるがその後は読めない状況です。STDERRORに飛んでも読み込み量は0です。
ここ の記述を見るとパイプは含まれていないように見えます。対象に含まれるようにしないといけないのではないのでしょうか?
-
MSDNには以下のような記述がありますが、その点は理解されていますか?
fWaitAll が FALSE の場合、lpHandles パラメータ内で(戻り値 - WAIT_OBJECT_0)番目のオブジェクトが待機条件を満たしたことを意味します。
この関数を呼び出して実行している間に複数のオブジェクトがシグナル状態になった場合は、それらのオブジェクトのうち、最小のインデックス番号が返ります。関数から抜けてきた時に必ずしも一つのオブジェクトだけがシグナル状態とは限らないと思います。
この場合、最初に見つかった物だけを処理してWait関数に戻った場合、シグナル状態は解除されていたりしないでしょうか。追伸:最後の部分を見落としてました。とっちゃんさんが書かれている通りみたいですね。
解決した時は、参考になったレスポンスの所にある[回答としてマーク]ボタンをクリックしてスレッドを締めましょう。 -
すいません。ざっとリファレンスを見た限りでは無名パイプのハンドルには SYNCHRONIZE アクセス権(WaitForができる条件)がついてないみたいですね。なので私の提示した待機(どれかを待つ処理)は使えないみたいです(名前付きパイプのほうは見てないのでわかりません)。
さて、そうなると、必要があるまで待機という方法はとれませんので、処理を終了するまでひたすらループという形にするしかないと思います(最初に変更した適度にタイムアウトさせながら処理を行うという形)。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/- 回答としてマーク アレックス 2011年2月15日 17:38
-
すいません。こっちフォロー入れ損ねてました。今回は待機する方法ではなく渡すオブジェクトに問題があったみたいで、直接的には当てはまらない部分もあり、横に並べる形にしてしまいました。
でも、複数待機する場合、どれが条件を満たしているか?については、PATIOさんの言う通り待機ハンドル配列の前から順番になっています(どれか一つしか値を返せないためある意味仕方のないこと)。
そのため、ハンドルに格納する優先順位を考慮しないと、いつまでもチェックしきれないままということは起こりえます。
おそらく今回はこれが当てはまることはないと思いますが、動作的には類似した問題の可能性も十二分にあります(単に待機順変えたら問題なくなるということも起こり得る)。
もしかしたらプロセスハンドルを先頭に持ってきたら解決する。。。ということもあるかもしれません。文面からはわかりませんでしたが。
わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/