1999/09/14「コンソールアプリを,DOS 窓なしで GUI から呼び出す。」だが,この方法だとデータを得られない,という読者さんからのご指摘を頂く。読んでくれた人がいるというだけでもありがたいが,メールまで頂くと非常に嬉しい。
以下,頂いたサンプル。(見やすくするためと簡略化のため,改行や出力先等,多少変更してあります。)
// コマンドライン LPTSTR lpCommandLine; lpCommandLine = "xxx.exe"; // 標準出力用パイプ HANDLE hReadPipe = NULL, hWritePipe = NULL; // エラー出力用パイプ HANDLE hErrReadPipe = NULL, hErrWritePipe = NULL; // セキュリティ属性(ハンドル継承を指定) SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; // 標準出力パイプの作成 ::CreatePipe(&hReadPipe, &hWritePipe, &sa, 8192); // エラー出力パイプの作成 ::CreatePipe(&hErrReadPipe, &hErrWritePipe, &sa, 8192); STARTUPINFO StartupInfo; PROCESS_INFORMATION ProcessInfo; ::ZeroMemory(&StartupInfo, sizeof(STARTUPINFO)); StartupInfo.cb = sizeof(STARTUPINFO); // ハンドルの継承を指定 StartupInfo.dwFlags = STARTF_USESTDHANDLES; // DOS窓を表示しない StartupInfo.wShowWindow = SW_HIDE; // 標準出力ハンドルとエラー出力ハンドルを設定 StartupInfo.hStdOutput = hWritePipe; StartupInfo.hStdError = hErrWritePipe; // コンソールアプリ起動 if (::CreateProcess( NULL, lpCommandLine, NULL, NULL, TRUE, // ハンドルの継承 DETACHED_PROCESS, // DOS窓を表示しないための指定 NULL, NULL, &StartupInfo, &ProcessInfo)) { // パイプ内容受け取り用バッファ char bufStdOut[8192], bufErrOut[8192]; DWORD dwStdOut = 0, dwErrOut = 0; DWORD dwRet; // プロセス起動中のパイプ内容受け取り処理 while ( (dwRet = ::WaitForSingleObject(ProcessInfo.hProcess, 0)) != WAIT_ABANDONED) { memset(bufStdOut, 0, sizeof(bufStdOut)); memset(bufErrOut, 0, sizeof(bufErrOut)); // 標準出力パイプの内容を調べる ::PeekNamedPipe(hReadPipe, NULL, 0, NULL, &dwStdOut, NULL); if (dwStdOut > 0) { // 内容が存在すれば、読み取る ::ReadFile(hReadPipe, bufStdOut, sizeof(bufStdOut) - 1, &dwStdOut, NULL); // bufStdOut にパイプ出力が入る } // 同様にエラー出力の処理 ::PeekNamedPipe(hErrReadPipe, NULL, 0, NULL, &dwErrOut, NULL); if (dwErrOut > 0) { ::ReadFile(hErrReadPipe, bufErrOut, sizeof(bufErrOut) - 1, &dwErrOut, NULL); // bufErrOut にエラー出力が入る } // メッセージキューを取得し、存在すれば、処理を促す MSG msg; if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } // プロセス終了なら、ループを抜ける if (dwRet == WAIT_OBJECT_0) break; } // プロセスハンドルとスレッドハンドルを閉じる DWORD res; ::GetExitCodeProcess(ProcessInfo.hProcess, &res); CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); } // すべてのパイプを閉じる ::CloseHandle(hWritePipe); ::CloseHandle(hReadPipe); ::CloseHandle(hErrWritePipe); ::CloseHandle(hErrReadPipe);
修正個所としては,
- CreateProcess の bInheritHandles,dwCreationFlags の値。
- 全体的なエラーチェック。
というところ。
# ちなみに 1999/09/14 では Win98 で動いているのを確認しているので,多分 NT 等では動かなかったのでは…と思う。いずれにしろ上のコードならば大丈夫でしょう。
# 2006/07/10 CreateProcess第3引数にsaを渡さないようにした。将来プロセスハンドルを子に継承させるならsaを渡すのだが,この目的では不要。
その後読者さんとは話がスクロールバーの話題へ。スクロールバーを MFC で使っていると,OnVScroll() の SB_THUMBPOSITION,SB_THUMBTRACK において,nPos が 16bit を越えると負の値となる。また,MFC のヘルプには
nMinPos と nMaxPos で指定された値の差は、32、767以下でなければいけません。
ということが書いてあったので,てっきりスクロールバーは 16bit しか扱えないんだと思っていたが,これは WM_VSCROLL が nPos を 16bit しか渡してくれないから,ということだ。(nPos = (short int) HIWORD(wParam); となっている。)
読者さんの指摘通り,OnVScroll() 内で GetScrollInfo() を直接呼び出し,スクロール位置を得ることで 32bit の値をとることができた。
いや,いつも身内だけでやってるのでこういうのはいいですね。上記以外でも,もしこの日記を読んだ方がいて,何か変だと思うことがあったら是非知らせて欲しいです。