Windowsでリダイレクトされた標準入力と標準出力を持つ子プロセスを作る
- CreatePipe()で, 親プロセス側で匿名パイプを使って, 子プロセスのリダイレクト用標準入力(ハンドル)と標準出力(ハンドル)を作る.
- SetHandleInformation()で, 子プロセスの標準入力の
WR
と標準出力のRD
を閉じる.
- SETUPINFO構造体で, 親プロセスの標準入力の
WR
と子プロセス標準入力のRD
をつなぐ. (リダイレクト) ※名前付きパイプはプロセスI/Oもリダイレクトできる.
- 同様に, 親プロセスの標準出力の
RD
と子プロセス標準出力のWR
をつなぐ.
- 子プロセスを作成.
- 親プロセスからリダイレクト用の標準入力(ハンドル)と標準出力(ハンドル)を子プロセスに継承させる.
#define NOMAXMIN
#include <windows.h>
#include <tchar.h>
#include <cstdio>
#include <strsafe.h>
#define BUFSIZE 4096
// ハンドル(標準入力と標準出力)
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL; // 後で閉じる
HANDLE g_hChildStd_OUT_Rd = NULL; // あとで閉じる
HANDLE g_hChildStd_OUT_Wr = NULL;
// ファイルハンドル
HANDLE g_hInputFIle = NULL;
// 関数宣言
void CreateChildProcess();
void WriteToPipe();
void ReadFromPipe();
void ExitError(PTSTR);
// entry point
int _tmain(int argc, TCHAR** argv)
{
std::printf("Start Parent\n");
// 子プロセスのセキュリティ設定
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = true;
saAttr.lpSecurityDescriptor = NULL;
// 子プロセスの標準出力のパイプを作る
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
{
ExitError(_T("Stdout CreatePipe"));
}
// 子プロセスの標準出力の`Rd`を閉じる
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
{
ExitError(_T("Stdout SetHandleInformation"));
}
// 子プロセスの標準入力のパイプを作る
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
{
ExitError(_T("Stdin CreatePipe"));
}
// 子プロセスの標準入力の`Wr`を閉じる
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
{
ExitError(_T("Stdio SetHandleInformation"));
}
// 子プロセス作成
CreateChildProcess();
if (argc == 1)
{
ExitError(_T("Not enough args"));
}
// ファイル読み込み
g_hInputFileHandle = CteateFile(
argv[1],
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_READONLY,
NULL
);
if (g_hInputFileHandle == INVALID_HANDLE_VALUE)
{
ExitError(_T("File Error"));
}
// 子プロセスの標準入力に親プロセスから書き込む.
// 一旦, パイプのバッファ(4096)に溜まる. 非同期.
// パイプに書き込む前に子プロセスの起動を待たなくてよい.
// 親プロセスがパイプにデータをプッシュ前に子プロセスがハイプからデータをポップしようとすると子プロセスは一時停止する.
// ただし, 4096以上を一気に書き込むとバッファから漏れるデータがあると思う. その場合, 漏れたデータは消失?
WriteToPipe();
std::printf("Write to Stdin of child process by push data to Stdout of parent process.\n");
// 子プロセスからの標準出力から読み出す
ReadFromPipe();
return 0;
}
void CreateChildProcess()
{
TCHAR szCmdline[] = _T("child");
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = false;
// PROCESS_INFORMATION構造体の初期化
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// STARTUPINFO構造体の初期化. ここで, 子プロセスにリダイレクト用の標準入力と標準出力を継承する.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// 子プロセス作成; CreateProcessAsUse, CreateProcessWithLogonもある. ユーザーアカウントレベルのセキュリティ属性をもつ子プロセスを作る
bSuccess = CreateProcess(
NULL, // 通常, NULL. NULLにすると第二引数のコマンドライン文字列で子プロセス名を指定.
szCmdline, // コマンドライン引数
NULL, // プロセスセキュリティ属性
NULL, // プライマリースレッドセキュリティ属性
true, // ハンドルを継承するか否か
0, // 作成フラグ
NULL, // 親の環境変数を継承するか否か
NULL, // 親のカレントディレクトリを継承するか否か
&siStartInfo, // 標準入力,標準出力の設定
&piProcInfo // 子プロセスの情報を受け取るための変数入力
);
if (!bSuccess)
{
// 子プロセスとプライマリースレッドへのハンドルを閉じる
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
// 子プロセスの標準入力`Rd`と標準出力`Wr`を閉じる
CloseHandle(g_hChildStd_IN_Rd);
CloseHandle(g_hChildStd_OUT_Wr);
}
// piProcInfoを確認して子プロセスの状態をチェック
// ...
}
- 親プロセスで読み込んだファイルデータを子プロセスの標準入力に渡す
void WriteToPipe()
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess;
for (;;)
{
bSuccess = ReadFile(g_hInputFileHandle, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess || dwRead == 0)
break;
// 子プロセスの標準入力に書き込み
bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf, dwRead, &dwWritten, NULL);
if (!bSuccess)
break;
}
// 子プロセスのg_hChildStd_IN_Wrハンドルを閉じると, 子プロセス側の標準入力からの読み取りが終わる
if (!CloseHandle(g_hChildStd_IN_Wr))
ExitError(_T("Failed to close g_hChildStd_IN_Wr"));
}
- 子プロセスの標準出力からのデータを親プロセスの標準出力に渡す
void ReadFromPipe()
{
DWORD dwRead, dwWritten;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = false;
HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
for (;;)
{
bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess || dwRead == 0)
break;
// 親プロセスの標準出力に渡す
bSuccess = WriteFile(hParentStdOut, chBuf, BUFSIZE, &dwWritten, NULL);
if (!bSuccess)
break;
}
}
- よくあるパターンのFormatMessage関数によるメッセージの取得
void ExitError(PTSTR lpszFunction)
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError(); // エラーメッセージの番号
FormatMessage(
FORMAT_MESSAGE_ALLOCATION_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0,
NULL);
lpDisplayBuf = (LPVOID)LocalAlloc(
LMEM_ZEROINIT,
lstrlen((LPCTSTR)lpMsgBuf)) + lstrlen((LPCTSTR)(lpFunction) + 40) * sizeof(TCHAR)
);
stringcohprintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
_T("%s failed with error %d : %s), lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, _T("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
ExitProcess(1);
}
#define NOMAXMIN
#include <windows.h>
#include <cstdio>
#define BUFSIZE 4096
int main(int, char**)
{
CHAR chBuf[BUFSIZE];
DWORD dwRead, dwWritten;
HANDLE hStdin, hStdout;
BOOL bSuccess = false;
// 標準入力と標準出力を取得
hStdin = GetStdHandle(STD_INPUT_HANDLE);
hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdout == INVALID_HANDLE_VALUE || hStdin == INVALID_HANDLE_VALUE)
return 1;
for (;;)
{
bSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL);
if (!bSuccess || dwRead == 0)
break;
bSuccess = WriteFile(hStdout, chBuf, BUFSIZE, &dwWritten, NULL);
if (!bSuccess)
break;
}
return 0;
}