5/25/2008

Capture Console App Output Using Pipe

  Pipe is an important and useful IPC mechanism on Windows Platform.Capturing console application output is often required for debugger or mulit-process architecture applications. The basic console output caputring task leverages stdio redirection provided by OS. Files can be used when redirect stdout/stdin, but Pipe provides more elegant way to do so. For example, it doesn't need extra file creation and deletion, write/read end can work in a pipeline(or consumer/producer) style, reader can work before writer finish all his tasks.

  Here is the code to combine anonymous Pipe and STDIO redirection to caputre console application output:
 1 #include <string>
 2 #include <sstream>
 3 #include <windows.h>
 4 #include <vector>
 5
 6 int __cdecl main(int argc, const char** argv)
 7 {
 8     (void)argc;
 9     (void)argv;
10     // connect pipe
11     SECURITY_ATTRIBUTES secAttr = {0};
12     secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
13     secAttr.lpSecurityDescriptor = 0;
14     secAttr.bInheritHandle = TRUE;
15     HANDLE hPipeRead = NULL, hPipeWrite = NULL;
16     if (!CreatePipe(&hPipeRead, &hPipeWrite, &secAttr, 0))
17     {
18         printf("CreatePipe Failed Due to Win32 Error:%d", GetLastError());
19         return -1;
20     }
21
22     // create dir process
23     const char * cmdLine = "cmd.exe /C dir /B /A:-D /O:D /T:W ";
24     STARTUPINFOA startInfo = {0};
25     startInfo.cb = sizeof(STARTUPINFOA);
26     startInfo.hStdOutput = hPipeWrite;
27     startInfo.dwFlags |= STARTF_USESTDHANDLES;
28     PROCESS_INFORMATION psInfo = {0};
29     if (!CreateProcessA(NULL, (LPSTR)cmdLine, NULL, NULL, TRUE, NULL, NULL, NULL, &startInfo, &psInfo))
30     {
31         printf("Create Dir Process Failed Due To Win32 Error:%d", GetLastError());
32         CloseHandle(hPipeRead);
33         CloseHandle(hPipeWrite);
34         return -1;
35     }
36
37     // read dir process output
38     DWORD dwRead = 0;
39     char dataBuf[MAX_PATH];
40     std::string outString;
41     outString.reserve(32 * 1024);
42     CloseHandle(hPipeWrite);
43     do
44     {
45         if (!ReadFile(hPipeRead, dataBuf, MAX_PATH, &dwRead, NULL))
46         {
47             if (GetLastError() == ERROR_BROKEN_PIPE)
48             // No more data
49             {
50                 break;
51             }
52             else
53             // Unkown error
54             {
55                 printf("Read From Pipe Failed Due to Win32 Error:%d", GetLastError());
56                 CloseHandle(hPipeRead);
57                 return -1;
58             }
59         }
60         if (dwRead != 0)
61         {
62             outString.append(dataBuf, dwRead);
63         }
64     } while (dwRead != 0);
65     CloseHandle(hPipeRead);
66
67     // parse output
68     std::stringstream outStream(outString);
69     std::string oneLine;
70     std::vector<std::string> fileVec;
71     oneLine.clear();
72     while (std::getline(outStream, oneLine))
73     {
74         if (!oneLine.empty())
75         {
76             // remove tailing nl/cr char
77             fileVec.push_back(oneLine.substr(0, oneLine.length() -1));
78         }
79         oneLine.clear();
80     }
81     return 0;
82 }


  NOTE on anonymous pipe:
1. Parent should use some way(other IPC mechanism) to pass Pipe handle(read/write) to child process.
2. Anonymous pipe returned handle is inheritable only when secAttr structure set bInheritHandle field to TRUE.
3. In order to make child process use those handles passed by parent, parent should set the 5th parameter to TRUE when create child process.
4. Anonymous pipe will exist if some read/write handle is connected to it.
5. If all write handles to anonymous pipe are closed, ReadFile() operations on that pipe will return ERROR_PIPE_BROKEN when all data are read out.

No comments: