コンソール プロセスを生成して標準ハンドルをリダイレクトする方法


概要


この資料では、標準入力ハンドルからの入力を受信する、または標準出力ハンドルに出力を送信する子プロセスの入出力をリダイレクトする方法について説明します。Win32 API を使用することにより、アプリケーションでコンソールの子プロセスを生成し、標準ハンドルをリダイレクトすることができます。この機能により、親プロセスは子プロセスとの間で入出力の送受信を行うことができます。


: コンソール ベースのアプリケーションには、入出力 (IO) 処理に標準ハンドルを使用しないものもあります。Win32 API では、このようなプロセスのリダイレクトはサポートされません。

詳細


STARTUPINFO 構造体を指定して CreateProcess() API を使用すると、コンソール ベースの子プロセスの標準ハンドルをリダイレクトすることができます。dwFlags メンバに STARTF_USESTDHANDLES を設定した場合は、STARTUPINFO の以下のメンバで、コンソール ベースの子プロセスの標準ハンドルを指定します。

HANDLE hStdInput - 子プロセスの標準入力ハンドル。
HANDLE hStdOutput - 子プロセスの標準出力ハンドル。
HANDLE hStdError - 子プロセスの標準エラー ハンドル。
これらのハンドルには、パイプ ハンドル、ファイル ハンドル、または、ReadFile() および WriteFile() API を使用して同期読み取りおよび同期書き込みを実行できる任意のハンドルを設定できます。これらのハンドルは継承可能である必要があり、また、CreateProcess() API で bInheritHandles パラメータに TRUE を指定して、継承可能なハンドルが子プロセスに継承されるようにする必要があります。親プロセスからリダイレクトする標準ハンドルが 1 つないし 2 つのみの場合は、特定のハンドルに対して GetStdHandle() を指定すると、子プロセスで、リダイレクトしない場合と同様に標準ハンドルが生成されます。たとえば、親プロセスで子プロセスの標準出力と標準エラーのみをリダイレクトする必要がある場合は、STARTUPINFO 構造体の hStdInput メンバに次のように設定します。

hStdInput = GetStdHandle(STD_INPUT_HANDLE);
: printf() や fprintf() などの C ランタイム関数を使用する子プロセスでは、リダイレクトを行うと処理速度が低下する場合があります。C ランタイム関数では、独立した入出力バッファが使用されます。リダイレクトを行うと、各 IO 呼び出しの後、これらのバッファがすぐにはフラッシュされない場合があります。その結果、printf() 呼び出しのリダイレクト パイプへの出力、または getch() 呼び出しからの入力がすぐにはフラッシュされず、遅延が発生し、それがときには長時間にわたります。この問題は、子プロセスで C ランタイム IO 関数を呼び出した後に毎回、入出力バッファをフラッシュすることにより、回避できます。C ランタイムの入出力バッファをフラッシュできるのは、そのバッファを使用する子プロセスのみです。プロセスで fflush() 関数を呼び出すことにより、そのプロセスの C ランタイム入出力バッファをフラッシュできます。


: Windows 95 および Windows 98 では、特定の子プロセスの標準ハンドルをリダイレクトするときに、さらに必要な手順があります。


次のサンプル コードでは、CreateProcess 呼び出しで指定された子プロセスの標準入力、標準出力、および標準エラーがリダイレクトされます。このサンプルでは、指定されたコンソール プロセス (Child.c) をリダイレクトします。

サンプル コード

   /*++

Copyright (c) 1998 Microsoft Corporation

Module Name:

Redirect.c

Description:
This sample illustrates how to spawn a child console based
application with redirected standard handles.

The following import libraries are required:
user32.lib

Dave McPherson (davemm) 11-March-98

--*/

#include<windows.h>
#pragma comment(lib, "User32.lib")
void DisplayError(char *pszAPI);
void ReadAndHandleOutput(HANDLE hPipeRead);
void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut,
HANDLE hChildStdIn,
HANDLE hChildStdErr);
DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam);

HANDLE hChildProcess = NULL;
HANDLE hStdIn = NULL; // Handle to parents std input.
BOOL bRunThread = TRUE;


void main ()
{
HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
HANDLE hInputWriteTmp,hInputRead,hInputWrite;
HANDLE hErrorWrite;
HANDLE hThread;
DWORD ThreadId;
SECURITY_ATTRIBUTES sa;


// Set up the security attributes struct.
sa.nLength= sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;


// Create the child output pipe.
if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0))
DisplayError("CreatePipe");


// Create a duplicate of the output write handle for the std error
// write handle. This is necessary in case the child application
// closes one of its std output handles.
if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite,
GetCurrentProcess(),&hErrorWrite,0,
TRUE,DUPLICATE_SAME_ACCESS))
DisplayError("DuplicateHandle");


// Create the child input pipe.
if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0))
DisplayError("CreatePipe");


// Create new output read handle and the input write handles. Set
// the Properties to FALSE. Otherwise, the child inherits the
// properties and, as a result, non-closeable handles to the pipes
// are created.
if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
GetCurrentProcess(),
&hOutputRead, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
DisplayError("DupliateHandle");

if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
GetCurrentProcess(),
&hInputWrite, // Address of new handle.
0,FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
DisplayError("DupliateHandle");


// Close inheritable copies of the handles you do not want to be
// inherited.
if (!CloseHandle(hOutputReadTmp)) DisplayError("CloseHandle");
if (!CloseHandle(hInputWriteTmp)) DisplayError("CloseHandle");


// Get std input handle so you can close it and force the ReadFile to
// fail when you want the input thread to exit.
if ( (hStdIn = GetStdHandle(STD_INPUT_HANDLE)) ==
INVALID_HANDLE_VALUE )
DisplayError("GetStdHandle");

PrepAndLaunchRedirectedChild(hOutputWrite,hInputRead,hErrorWrite);


// Close pipe handles (do not continue to modify the parent).
// You need to make sure that no handles to the write end of the
// output pipe are maintained in this process or else the pipe will
// not close when the child process exits and the ReadFile will hang.
if (!CloseHandle(hOutputWrite)) DisplayError("CloseHandle");
if (!CloseHandle(hInputRead )) DisplayError("CloseHandle");
if (!CloseHandle(hErrorWrite)) DisplayError("CloseHandle");


// Launch the thread that gets the input and sends it to the child.
hThread = CreateThread(NULL,0,GetAndSendInputThread,
(LPVOID)hInputWrite,0,&ThreadId);
if (hThread == NULL) DisplayError("CreateThread");


// Read the child's output.
ReadAndHandleOutput(hOutputRead);
// Redirection is complete


// Force the read on the input to return by closing the stdin handle.
if (!CloseHandle(hStdIn)) DisplayError("CloseHandle");


// Tell the thread to exit and wait for thread to die.
bRunThread = FALSE;

if (WaitForSingleObject(hThread,INFINITE) == WAIT_FAILED)
DisplayError("WaitForSingleObject");

if (!CloseHandle(hOutputRead)) DisplayError("CloseHandle");
if (!CloseHandle(hInputWrite)) DisplayError("CloseHandle");
}


///////////////////////////////////////////////////////////////////////
// PrepAndLaunchRedirectedChild
// Sets up STARTUPINFO structure, and launches redirected child.
///////////////////////////////////////////////////////////////////////
void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut,
HANDLE hChildStdIn,
HANDLE hChildStdErr)
{
PROCESS_INFORMATION pi;
STARTUPINFO si;

// Set up the start up info struct.
ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = hChildStdOut;
si.hStdInput = hChildStdIn;
si.hStdError = hChildStdErr;
// Use this if you want to hide the child:
// si.wShowWindow = SW_HIDE;
// Note that dwFlags must include STARTF_USESHOWWINDOW if you want to
// use the wShowWindow flags.


// Launch the process that you want to redirect (in this case,
// Child.exe). Make sure Child.exe is in the same directory as
// redirect.c launch redirect from a command line to prevent location
// confusion.
if (!CreateProcess(NULL,"Child.EXE",NULL,NULL,TRUE,
CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi))
DisplayError("CreateProcess");


// Set global child process handle to cause threads to exit.
hChildProcess = pi.hProcess;


// Close any unnecessary handles.
if (!CloseHandle(pi.hThread)) DisplayError("CloseHandle");
}


///////////////////////////////////////////////////////////////////////
// ReadAndHandleOutput
// Monitors handle for input. Exits when child exits or pipe breaks.
///////////////////////////////////////////////////////////////////////
void ReadAndHandleOutput(HANDLE hPipeRead)
{
CHAR lpBuffer[256];
DWORD nBytesRead;
DWORD nCharsWritten;

while(TRUE)
{
if (!ReadFile(hPipeRead,lpBuffer,sizeof(lpBuffer),
&nBytesRead,NULL) || !nBytesRead)
{
if (GetLastError() == ERROR_BROKEN_PIPE)
break; // pipe done - normal exit path.
else
DisplayError("ReadFile"); // Something bad happened.
}

// Display the character read on the screen.
if (!WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),lpBuffer,
nBytesRead,&nCharsWritten,NULL))
DisplayError("WriteConsole");
}
}


///////////////////////////////////////////////////////////////////////
// GetAndSendInputThread
// Thread procedure that monitors the console for input and sends input
// to the child process through the input pipe.
// This thread ends when the child application exits.
///////////////////////////////////////////////////////////////////////
DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam)
{
CHAR read_buff[256];
DWORD nBytesRead,nBytesWrote;
HANDLE hPipeWrite = (HANDLE)lpvThreadParam;

// Get input from our console and send it to child through the pipe.
while (bRunThread)
{
if(!ReadConsole(hStdIn,read_buff,1,&nBytesRead,NULL))
DisplayError("ReadConsole");

read_buff[nBytesRead] = '\0'; // Follow input with a NULL.

if (!WriteFile(hPipeWrite,read_buff,nBytesRead,&nBytesWrote,NULL))
{
if (GetLastError() == ERROR_NO_DATA)
break; // Pipe was closed (normal exit path).
else
DisplayError("WriteFile");
}
}

return 1;
}


///////////////////////////////////////////////////////////////////////
// DisplayError
// Displays the error number and corresponding message.
///////////////////////////////////////////////////////////////////////
void DisplayError(char *pszAPI)
{
LPVOID lpvMessageBuffer;
CHAR szPrintBuffer[512];
DWORD nCharsWritten;

FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpvMessageBuffer, 0, NULL);

wsprintf(szPrintBuffer,
"ERROR: API = %s.\n error code = %d.\n message = %s.\n",
pszAPI, GetLastError(), (char *)lpvMessageBuffer);

WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),szPrintBuffer,
lstrlen(szPrintBuffer),&nCharsWritten,NULL);

LocalFree(lpvMessageBuffer);
ExitProcess(GetLastError());
}

//////////////////////////////////////////////////////////////////////
// child.c
// Echoes all input to stdout. This will be redirected by the redirect
// sample. Compile and build child.c as a Win32 Console application and
// put it in the same directory as the redirect sample.
//
#include<windows.h>
#include<stdio.h>
#include<string.h>

void main ()
{
FILE* fp;
CHAR szInput[1024];


// Open the console. By doing this, you can send output directly to
// the console that will not be redirected.

fp = fopen("CON", "w");
if (!fp) {
printf("Error opening child console - perhaps there is none.\n");
fflush(NULL);
}
else
{

// Write a message direct to the console (will not be redirected).

fprintf(fp,"This data is being printed directly to the\n");
fprintf(fp,"console and will not be redirected.\n\n");
fprintf(fp,"Since the standard input and output have been\n");
fprintf(fp,"redirected data sent to and from those handles\n");
fprintf(fp,"will be redirected.\n\n");
fprintf(fp,"To send data to the std input of this process.\n");
fprintf(fp,"Click on the console window of the parent process\n");
fprintf(fp,"(redirect), and enter data from it's console\n\n");
fprintf(fp,"To exit this process send the string 'exit' to\n");
fprintf(fp,"it's standard input\n");
fflush(fp);
}

ZeroMemory(szInput,1024);
while (TRUE)
{
gets(szInput);
printf("Child echoing [%s]\n",szInput);
fflush(NULL); // Must flush output buffers or else redirection
// will be problematic.
if (!_stricmp(szInput,"Exit") )
break;

ZeroMemory(szInput,strlen(szInput) );

}
}

関連情報


MSDN ライブラリ SDK ドキュメント : CreateProcess(); STARTUPINFO 構造体


以下のフォルダにある Win32 Platform SDK のサンプルを継承します。

\MSSDK\samples\winbase\ipc\inherit