【翻译】创建并重定向子进程的标准输入输出
原文地址:http://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx
本节的实例描述了控制台进程如果通过CreateProcess函数创建子进程。同时展示了使用匿名管道重定向子进程的标准输入和标准输出句柄。当然,有名的管道也可以用来重定义进程的I/O。
CreatePipe函数使用SECURITY_ATTRIBUTES来创建可继承的读写端的管道。管道的读端作为子进程的标准输入,管道的写端作为子进程的标准输出。将这两个管道句柄设置到STARTUPINOF的结构体的标准句柄中(标准输入,标准输出,标准错误输出),这样子进程就可以继承这几个管道的句柄。
父进程使用管道的另外两端向子进程的标准输入中写入数据或者从子进程的标准输出读出数据。在STARTUPINFO结构体中,这些句柄(即父进程使用的另外两个句柄)也是可以承继的。然而这些句柄是不能被子进程继承(子进程中继承从标准输入中读取数据和向标准输出中写入数据)。因此,在创建子进程前,父进程调用SetHandleInfomation函数来保证向子进程标准输入中写入数据和从子进程标准输出中读出数据是不可以被继承的。更多信息,参阅Pipes。
下面的代码是的父进程的代码。它使用一个简单命令行参数:文本文件的名字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
#include <windows.h> #include <tchar.h> #include <stdio.h> #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); void WriteToPipe(void); void ReadFromPipe(void); void ErrorExit(PTSTR); int _tmain(int argc, TCHAR *argv[]) { SECURITY_ATTRIBUTES saAttr; printf("\n->Start of parent execution.\n"); //设置bInheritHandle标志,那么管道句柄都可以被继承. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; //为子进程的标准输出创建管道 if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) ) ErrorExit(TEXT("StdoutRd CreatePipe")); //设置子进程的标准输出的读操作不可以被子进程继承,只有父进程才可以读 if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) ) ErrorExit(TEXT("Stdout SetHandleInformation")); //为子进程的标准输入创建管道 if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) ErrorExit(TEXT("Stdin CreatePipe")); //设置子进程的标准输入的写操作不可以被子进程继承,只有父进程才可以写 if ( ! SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) ) ErrorExit(TEXT("Stdin SetHandleInformation")); //创建子进程 CreateChildProcess(); //关闭父进程中的被子进程继承的标准输入和标准输出 CloseHandle(g_hChildStd_OUT_Wr); CloseHandle(g_hChildStd_IN_Rd); if (argc == 1) ErrorExit(TEXT("Please specify an input file.\n")); g_hInputFile = CreateFile( argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); if ( g_hInputFile == INVALID_HANDLE_VALUE ) ErrorExit(TEXT("CreateFile")); //向子进程的标准备输入里写入数据 //数据被写入了管道的缓冲区,所以写数据不需要等到子进程完全启动 WriteToPipe(); printf( "\n->Contents of %s written to child STDIN pipe.\n", argv[1]); //从子进程的标准输出读出数据 printf( "\n->Contents of child process STDOUT:\n\n", argv[1]); ReadFromPipe(); printf("\n->End of parent execution.\n"); return 0; } void CreateChildProcess() { TCHAR szCmdline[]=TEXT("child"); PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; BOOL bSuccess = FALSE; ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); //指定标准输入输出的重定向句柄 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; //创建子进程 bSuccess = CreateProcess(NULL, szCmdline, //命令行 NULL, // NULL, // TRUE, //句柄可以被继承 0, // NULL, // NULL, // &siStartInfo, // &piProcInfo); // if ( ! bSuccess ) ErrorExit(TEXT("CreateProcess")); else { //关闭子进程的句柄和主线程句柄 //有些应用程序会暂时不关闭,从而通过句柄获取子进程的信息 CloseHandle(piProcInfo.hProcess); CloseHandle(piProcInfo.hThread); } } //从文件中读出数据并写入子进程的标准输入 //文件结束即终止 void WriteToPipe(void) { DWORD dwRead, dwWritten; CHAR chBuf[BUFSIZE]; BOOL bSuccess = FALSE; for (;;) { bSuccess = ReadFile(g_hInputFile, chBuf, BUFSIZE, &dwRead, NULL); if ( ! bSuccess || dwRead == 0 ) break; bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf, dwRead, &dwWritten, NULL); if ( ! bSuccess ) break; } if ( ! CloseHandle(g_hChildStd_IN_Wr) ) ErrorExit(TEXT("StdInWr CloseHandle")); } //从子进程中标准输出中读出数据并写父进程的标准输出上 void ReadFromPipe(void) { 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, dwRead, &dwWritten, NULL); if (! bSuccess ) break; } } void ErrorExit(PTSTR lpszFunction) { LPVOID lpMsgBuf; LPVOID lpDisplayBuf; DWORD dw = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_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)lpszFunction)+40)*sizeof(TCHAR)); StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR), TEXT("%s failed with error %d: %s"), lpszFunction, dw, lpMsgBuf); MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); LocalFree(lpMsgBuf); LocalFree(lpDisplayBuf); ExitProcess(1); } |
下面是子进程的代码。它使用STDIN和STDOUT的继承句柄去访问父进程创建的管道。父进程从它的输入文件中读出并将读出的数据写到管道中。子进程通过过STDIN的管道读取数据和通过STDOUT的管道写入数据。父进程能过管道的读端读出数据并能过标准输出STDOUT显示出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <windows.h> #include <stdio.h> #define BUFSIZE 4096 int main(void) { CHAR chBuf[BUFSIZE]; DWORD dwRead, dwWritten; HANDLE hStdin, hStdout; BOOL bSuccess; hStdout = GetStdHandle(STD_OUTPUT_HANDLE); hStdin = GetStdHandle(STD_INPUT_HANDLE); if ( (hStdout == INVALID_HANDLE_VALUE) || (hStdin == INVALID_HANDLE_VALUE) ) ExitProcess(1); // Send something to this process's stdout using printf. printf("\n ** This is a message from the child process. ** \n"); // This simple algorithm uses the existence of the pipes to control execution. // It relies on the pipe buffers to ensure that no data is lost. // Larger applications would use more advanced process control. for (;;) { // Read from standard input and stop on error or no data. bSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL); if (! bSuccess || dwRead == 0) break; // Write to standard output and stop on error. bSuccess = WriteFile(hStdout, chBuf, dwRead, &dwWritten, NULL); if (! bSuccess) break; } return 0; } |
本文还可以解决一个问题就是利用c库的system,_popen创建的进进程出现黑窗口的问题。另外原文件的父进程在创建子进程后增加了两行代码:
1 2 3 |
//关闭父进程中的被子进程继承的标准输入和标准输出 CloseHandle(g_hChildStd_OUT_Wr); CloseHandle(g_hChildStd_IN_Rd); |
有点类似于pipe后,父进程关闭fd[1]子进程关闭fd[0]类似,这是子进程没有继承向子进程标准输入中写数据和向子进程标准输出读数据,因为父进程设置这两个句柄不可以继承。同时父进程也应该关闭自己的向子进程标准输入中读数据和向子进程中标准输出中写数据的句柄。