04 윈도우 콘솔(console) 입출력 리다이렉션(Input/Out Redirection)

원문 : https://kkamagui.tistory.com/85

들어가기 전에...

첫번째 방법

** 원문 :** http://cafe.naver.com/winmain.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=62

HOWTO: Spawn Console Processes with Redirected Standard Handles

Q190351

SUMMARY

This article describes how to redirect the input and output of a child process that receives input from the standard input handle or sends output to the standard output handle. The Win32 API enables applications to spawn a child console process with redirected standard handles. This feature allows a parent process to send and receive the input and output of the child process.

이 글은 표준 입력 핸들에서 입력을 가져오거나 표준 출력 핸들로 출력을 보내는 자식 프로세스의 입출력을 리다이렉트(redirect)하는 방법을 기술한다. Win32 API는 애플리케이션이 자식 콘솔 프로세스를 리다이렉트된 표준 핸들과 함께 생성할 수 있도록 해준다. 이 기능은 부모 프로세스가 자식 프로세스의 입출력을 보내거나 가져오는 일을 가능하게 해준다.

NOTE: Some console based applications do not use the standard handles for their input/output (IO) operations. The Win32 API does not support redirection of these processes.

NOTE: 몇몇 콘솔 기반 애플리케이션은 입출력 처리에 있어 표준 핸들을 사용하지 않는다. Win32 API는 이러한 프로세스들의 리다이렉트를 지원하지 않는다.

MORE INFORMATION

The CreateProcess() API through the STARTUPINFO structure enables you to redirect the standard handles of a child console based process. If the dwFlags member is set to STARTF_USESTDHANDLES, then the following STARTUPINFO members specify the standard handles of the child console based process:

CreateProcess() API를 통해 넘겨지는 STARTUPINFO 구조체는 여러분이 자식 콘솔 기반 프로세스의 표준 핸들을 리다이렉트할 수 있도록 해준다. dwFlags의 숫자가 STARTF_USESTDHANDLES로 설정되면, 나머지 STARTUPINFO 멤버들은 자식 콘솔 기반 프로세스의 표준 핸들을 지정한다.

HANDLE hStdInput - Standard input handle of the child process.
HANDLE hStdOutput - Standard output handle of the child process.
HANDLE hStdError - Standard error handle of the child process.

You can set these handles to either a pipe handle, file handle, or any handle that can do synchronous reads and writes through the ReadFile() and WriteFile() API. The handles must be inheritable and the CreateProcess() API must specify that inheritable handles are to be inherited by the child process by specifying TRUE in the bInheritHandles parameter. If the parent process only wishes to redirect one or two standard handles, specifying GetStdHandle() for the specific handles causes the child to create the standard handle as it normally would without redirection. For example, if the parent process only needs to redirect the standard output and error of the child process, then the hStdInput member of the STARTUPINFO structure is filled as follows:

여러분은 이 핸들들을 파이프 핸들, 파일 핸들 또는 ReadFile()과 WriteFile() API를 통해 동기화된 읽기와 쓰기를 할 수 있는 어떠한 핸들로도 설정할 수 있다. 이 핸들들은 상속 가능해야 하며, CreateProcess API는 bInheritHandles 파라미터에 TRUE를 설정함으로써 이 상속 가능한 핸들들이 상속되도록 설정해야 한다. 부모 프로세스가 한 개나 두 개의 표준 핸들만을 리다이렉트하길 원한다면, GetStdHandle()을 그 핸들에 지정하여 자식 프로세스가 리다이렉트가 없는 것처럼 정상적으로 표준 핸들을 생성하도록 한다. 예를 들어 부모 프로세스가 자식 프로세스의 표준 출력과 에러 핸들만을 리다이렉트할 필요가 있다면, STARTUPINFO 구조체의 hStdInput 멤버는 다음과 같이 채워져야 한다:

hStdInput = GetStdHandle(STD\_INPUT\_HANDLE);

NOTE: Child processes that use such C run-time functions as printf() and fprintf() can behave poorly when redirected. The C run-time functions maintain separate IO buffers. When redirected, these buffers might not be flushed immediately after each IO call. As a result, the output to the redirection pipe of a printf() call or the input from a getch() call is not flushed immediately and delays, sometimes-infinite delays occur. This problem is avoided if the child process flushes the IO buffers after each call to a C run-time IO function. Only the child process can flush its C run-time IO buffers. A process can flush its C run-time IO buffers by calling the fflush() function.

NOTE: printf()와 fprintf()와 같은 C 런타임 함수들을 사용하는 자식 프로세스들은 리다이렉트되었을 때 이상하게 작동할 수 있다. C 런타임 함수들은 별도의 IO 버퍼들을 유지한다. 리다이렉트되었을 때, 이 버퍼들은 IO 호출이 끝날 때마다 즉시 버퍼를 비우지(flush) 않을 수도 있다. 결과적으로, printf() 호출의 리다이렉트 파이프로의 출력이나 getch() 호출로부터의 입력은 즉시 버퍼를 비우지 않고 지연되고, 얼마간에서-무한한 지연이 발생한다. 이 문제는 자식 프로세스가 C 런타임 IO 함수에 대한 호출이 끝날때마다 IO 버퍼를 비운다면 피할 수 있다. 오직 자식 프로세스만이 자신의 C 런타임 IO 버퍼를 비울 수 있다. 프로세스는 fflush() 함수를 호출함으로써 C 런타임 IO 버퍼를 비울 수 있다.

NOTE: Windows 95 and Windows 98 require an extra step when you redirect the standard handles of certain child processes. For additional information, please see the following article in the Microsoft Knowledge Base:

NOTE: 윈도우 95와 윈도우 98은 어떤 자식 프로세스의 표준 핸들을 리다이렉트할 때 추가적인 과정이 필요하다. 추가적인 정보에 대해서는 마이크로소프트 Knowledge Base에 있는 다음 글을 보기 바란다.

Q150956 INFO: Redirection Issues on Windows 95 MS-DOS Applications

The following sample redirects the standard input, output, and error of the child process specified in the CreateProcess call. This sample redirects the provided console process (Child.c).

다음 예제는 CreateProcess 호출에서 지정된 자식 프로세스의 표준 입력 출력, 에러를 리다이렉트한다. 이 예제는 제공된 콘솔 프로세스(Child.c)를 리다이렉트한다.

Sample Code

/*++
    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>

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) );
    }
 }

REFERENCES

MSDN Library SDK documentation: CreateProcess(); STARTUPINFO structure
Inherit sample in the Win32 Platform SDK under:
\MSSDK\samples\winbase\ipc\inherit

Additional query words: Inheritance redirection redirected stdhandles

Keywords : kbAPI kbConsole kbIPC kbKernBase kbOSWinNT400 kbOSWin2000 kbSDKPlatform kbOSWin95 kbOSWin98 kbFAQ kbDSupport kbGrpDSKernBase
Issue type : kbhowto
Technology : kbAudDeveloper kbWin32sSearch kbWin32API

두번째 방법

원문 http://www.codeproject.com/threads/redir.asp

첨부 : **[redir_demo.zip](http://kkamagui.springnote.com/pages/410158/attachments/171448 "redir_demo.zip"), [redir_src.zip**](http://kkamagui.springnote.com/pages/410158/attachments/171449 "redir_src.zip")

Introduction

To redirect the input/output of a console application is interesting and useful. You can display the child's output in a window (just like Visual Studio's output window), or search some keywords in the output string to determine if the child process has completed its work successfully. An old, 'ugly' DOS program could become an useful component of your fancy Win32 GUI program.

My idea is to develop a simple, easy to use redirector class which can redirect an arbitrary console, and won't be affected by the behavior of the child process.

Background

The technique of redirecting the input/output of a console process is very sample: The CreateProcess() API through the STARTUPINFO structure enables us to redirect the standard handles of a child console based process. So we can set these handles to either a pipe handle, file handle, or any handle that we can read and write. The detail of this technique has been described clearly in MSDN: HOWTO: Spawn Console Processes with Redirected Standard Handles.

However, MSDN's sample code has two big problem. First, it assumes the child process will send output at first, then wait for input, then flush the output buffer and exit. If the child process doesn't behave like that, the parent process will be hung up. The reason of this is the ReadFile() function remains blocked untill the child process sends some output, or exits.

Second, It has problem to redirect a 16-bit console (including console based MS-DOS applications.) On Windows 9x, ReadFile remains blocked even after the child process has terminated; On Windows NT/XP, ReadFile always returns FALSE with error code set to ERROR_BROKEN_PIPE if the child process is a DOS application.

Solving the block problem of ReadFile

To prevent the parent process from being blocked by ReadFile, we can simply pass a file handle as stdout to the child process, then monitor this file. A more simple way is to call PeekNamedPipe() function before calling ReadFile(). The PeekNamedPipe function checks information about data in the pipe, then returns immediately. If there's no data available in the pipe, don't call ReadFile.

By calling PeekNamedPipe before ReadFile, we also solve the block problem of redirecting a 16-bit console on Windows 9x.

The class CRedirector creates pipes and launchs the child process at first. then creates a listener thread to monitor the output of the child process. This is the main loop of the listener thread:

for (;;)
    {
        // redirect stdout till there's no more data.
        nRet = pRedir->RedirectStdout();
        if (nRet <= 0)
            break;

        // check if the child process has terminated.
        DWORD dwRc = ::WaitForMultipleObjects(
            2, aHandles, FALSE, pRedir->m_dwWaitTime);
        if (WAIT_OBJECT_0 == dwRc)      // the child process ended
        {
            ...
            break;
        }
        if (WAIT_OBJECT_0+1 == dwRc)    // m_hEvtStop was signalled, exit
        {
            ...
            break;
        }
    }

This is the main loop of the RedirectStdout() function:

for (;;)
    {
        DWORD dwAvail = 0;
        if (!::PeekNamedPipe(m_hStdoutRead, NULL, 0, NULL,
            &dwAvail, NULL))    // error, the child process might ended
            break;

        if (!dwAvail)           // no data available, return
            return 1;

        char szOutput[256];
        DWORD dwRead = 0;
        if (!::ReadFile(m_hStdoutRead, szOutput, min(255, dwAvail),
            &dwRead, NULL) || !dwRead)  
                 // error, the child process might ended
            break;

        szOutput[dwRead] = 0;
        WriteStdOut(szOutput);          // display the output
    }

WriteStdOut is a virtual member function. It does nothing in CRedirector class. However it can be overrided to achieve our specific target, like I did in the demo project:

int nSize = m_pWnd->GetWindowTextLength();  
             // m_pWnd points to a multiline Edit control
    m_pWnd->SetSel(nSize, nSize);
    m_pWnd->ReplaceSel(pszOutput);      
           // add the message to the end of Edit control

To redirect DOS console based applications on NT/2000/XP

MSDN's solution is to launch an intermediate Win32 Console application as a stub process between the Win32 parent and the 16-bit console based child. In fact the DOS prompt program (on NT/XP it's cmd.exe, on 9x it's command.com) is a natural stub process we just need. We can test this in RedirDemo.exe:

  1. Input 'cmd.exe' in Command Editbox, then press Run button.
  2. Input the name of the 16-bit console based application (dosapp.exe for example) in the Input Editbox, then press Input button. Now we can see the output of the 16-bit consol.
  3. Input 'exit' in the Input Editbox, then press Input button to terminate cmd.exe

Apparently this is not a good solution because it's too complicated. A more effective way is to use a batch file as the stub. Edit stub.bat file like this:

%1 %2 %3 %4 %5 %6 %7 %8 %9

Then run a command like 'stub.bat dosapp.exe', then the 16-bit DOS console application runs OK.

About nickadams

  Nick Adams is one of my favorite figures in Hemingway's stories. I use it because Jeff Lee has been occupied on Codeproject.

Click here to view nickadams's online profile.

Other popular Threads, Processes & IPC articles:

+ Recent posts