Tuesday, August 18, 2009

How to Read console processes Output with redirected standard handles

The CreateProcess() API through the STARTUPINFO structure enables you to redirect the standard handles of a child console based process.

Some of the Undocumented facts about CreateProcess()

http://www.catch22.net/tuts/undoc01


If the dwFlags member is set to STARTF_USESTDHANDLES, then the following STARTUPINFO members specify the standard handles of the child console based process.

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

               hStdInput = GetStdHandle(STD_INPUT_HANDLE);

To Execute a Console Application From VC++ and retrieve the messages shown in the console.

Three ways you can achieve this.

· Through pipe handle.

· Through file handle.

· Through ReadFile() and WriteFile() API’s.

We are discussing here the First way i.e. by using Pipe we Execute the console application and will retrieve the messages shown in the console.

In many situations we may need to execute a console application or a DOS application from within our MFC application. ShellExcecute can be used for this purpose, but can only be used to run the application. Messages shown in the console is not reachable.

First, we should talk about pipes. A pipe in Windows is simply a method of communication, often between process. The SDK defines a pipe as "a communication conduit with two ends; a process with a handle to one end can communicate with a process having a handle to the other end." In our case, we are using "anonymous" pipes, one-way pipes that "transfer data between a parent process and a child process or between two child processes of the same parent process." It's easiest to imagine a pipe as its namesake. An actual pipe running between processes that can carry data.

We are using anonymous pipes because the console app we are spawning is a child process. We use the CreatePipe function which will create an anonymous pipe and return a read handle and a write handle. We will create two pipes, on for stdin and one for stdout. We will then monitor the read end of the stdout pipe to check for display on our child process. Every time there is something availabe for reading, we will display it in our app. Consequently, we check for input in our app and send it off to the write end of the stdin pipe.

Example for the above is shown below.

The function ExecuteExternalFile, takes two arguments:

  • The application to be executed.
  • The arguments.
CString ExecuteExternalFile (CString csExeName, CString csArguments)
{
  CString csExecute;
  csExecute=csExeName + " " + csArguments;
  
  SECURITY_ATTRIBUTES secattr; 
  ZeroMemory(&secattr,sizeof(secattr));
  secattr.nLength = sizeof(secattr);
  secattr.bInheritHandle = TRUE;
 
  HANDLE rPipe, wPipe;
 
  //Create pipes to write and read data
 
  CreatePipe(&rPipe,&wPipe,&secattr,0);
  //
 
  STARTUPINFO sInfo; 
  ZeroMemory(&sInfo,sizeof(sInfo));
  PROCESS_INFORMATION pInfo; 
  ZeroMemory(&pInfo,sizeof(pInfo));
  sInfo.cb=sizeof(sInfo);
  sInfo.dwFlags=STARTF_USESTDHANDLES;
  sInfo.hStdInput=NULL; 
  sInfo.hStdOutput=wPipe; 
  sInfo.hStdError=wPipe;
  char command[1024]; strcpy(command,  
          csExecute.GetBuffer(csExecute.GetLength()));
 
  //Create the process here.
 
  CreateProcess(0 command,0,0,TRUE,
          NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW,0,0,&sInfo,&pInfo);
  CloseHandle(wPipe);
 
  //now read the output pipe here.
 
  char buf[100];
  DWORD reDword; 
  CString m_csOutput,csTemp;
  BOOL res;
  do
  {
      res=::ReadFile(rPipe,buf,100,&reDword,0);
      csTemp=buf;
      m_csOutput+=csTemp.Left(reDword);
  }while(res);
  return m_csOutput;
}

Above example is taken from:

http://www.codeproject.com/KB/cpp/9505Yamaha_1.aspx




1 comment:

  1. Great stuff, even for non techies

    Thanks for such a useful information in easy to follow language

    ReplyDelete