如何使用CreateProcess()和CreatePipe()从cmd.exe读取输出
我一直在尝试创build一个执行cmd.exe
的subprocess,命令行指定/K dir
。 目的是使用pipe道将命令的输出读回父进程。
我已经得到了CreateProcess()
工作,但涉及pipe道的步骤正在给我带来麻烦。 使用pipe道,新的控制台窗口不显示(像以前一样),并且父进程停留在对ReadFile()
的调用中。
有谁知道我做错了什么?
#include <Windows.h> #include <stdio.h> #include <tchar.h> #define BUFFSZ 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; int wmain(int argc, wchar_t* argv[]) { int result; wchar_t aCmd[BUFFSZ] = TEXT("/K dir"); // CMD /? STARTUPINFO si; PROCESS_INFORMATION pi; SECURITY_ATTRIBUTES sa; printf("Starting...\n"); ZeroMemory(&si, sizeof(STARTUPINFO)); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); // Create one-way pipe for child process STDOUT if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0)) { printf("CreatePipe() error: %ld\n", GetLastError()); } // Ensure read handle to pipe for STDOUT is not inherited if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { printf("SetHandleInformation() error: %ld\n", GetLastError()); } // Create one-way pipe for child process STDIN if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0)) { printf("CreatePipe() error: %ld\n", GetLastError()); } // Ensure write handle to pipe for STDIN is not inherited if (!SetHandleInformation(g_hChildStd_IN_Rd, HANDLE_FLAG_INHERIT, 0)) { printf("SetHandleInformation() error: %ld\n", GetLastError()); } si.cb = sizeof(STARTUPINFO); si.hStdError = g_hChildStd_OUT_Wr; si.hStdOutput = g_hChildStd_OUT_Wr; si.hStdInput = g_hChildStd_IN_Rd; si.dwFlags |= STARTF_USESTDHANDLES; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = NULL; // Pipe handles are inherited sa.bInheritHandle = true; // Creates a child process result = CreateProcess( TEXT("C:\\Windows\\System32\\cmd.exe"), // Module aCmd, // Command-line NULL, // Process security attributes NULL, // Primary thread security attributes true, // Handles are inherited CREATE_NEW_CONSOLE, // Creation flags NULL, // Environment (use parent) NULL, // Current directory (use parent) &si, // STARTUPINFO pointer &pi // PROCESS_INFORMATION pointer ); if (result) { printf("Child process has been created...\n"); } else { printf("Child process could not be created\n"); } bool bStatus; CHAR aBuf[BUFFSZ + 1]; DWORD dwRead; DWORD dwWrite; // GetStdHandle(STD_OUTPUT_HANDLE) while (true) { bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL); if (!bStatus || dwRead == 0) { break; } aBuf[dwRead] = '\0'; printf("%s\n", aBuf); } // Wait until child process exits WaitForSingleObject(pi.hProcess, INFINITE); // Close process and thread handles CloseHandle(pi.hProcess); CloseHandle(pi.hThread); printf("Stopping...\n"); return 0; }
我觉得你做得对。 但是,cmd.exe在启动和您的ReadFile块之后不会打印任何数据或非常少量的数据。 如果你移动你的周期
while (true) { bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL); if (!bStatus || dwRead == 0) { break; } aBuf[dwRead] = '\0'; printf("%s\n", aBuf); }
进入后台线程并运行其他循环,这将读取您的输入,并将其发送到cmd.exe,我想你可以看到任何效果。 您可以使读取缓冲区更小(例如16个字节)。
这是一个例子(从一个更大的程序取得)的线程,做你正在寻找。 它为它创建的过程创建stdout和stderr的管道,然后进入读取这些管道的循环,直到程序结束。
DWORD WINAPI ThreadProc(LPVOID lpParameter) { #define EVENT_NAME "Global\\RunnerEvt" HANDLE hev; SECURITY_ATTRIBUTES psa; InitSAPtr(&psa); DWORD waitRc; DWORD bytesRead; int manual_triggered = 1; hev = CreateEvent(&psa, FALSE, FALSE, EVENT_NAME); // Create pipes we'll read for(;;) { if (manual_triggered) { waitRc = WAIT_OBJECT_0; manual_triggered = 0; } else { waitRc = WaitForSingleObject(hev, 500); } if (waitRc == WAIT_OBJECT_0) { `logprint`f(LOG_DBG, "Received command to run process\n"); CreateChildOutFile(); stdOutEvt = CreateEvent(&psa, TRUE, FALSE, 0); stdOutOvl.hEvent = stdOutEvt; stdErrEvt = CreateEvent(&psa, TRUE, FALSE, 0); stdErrOvl.hEvent = stdErrEvt; gStdOutReadHand = CreateNamedPipe(STD_OUT_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE, PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa); if (gStdOutReadHand == INVALID_HANDLE_VALUE) { log(LOG_DBG, "Error %d on create STDOUT pipe\n", GetLastError()); } gStdErrReadHand = CreateNamedPipe(STD_ERR_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE, PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa); if (gStdErrReadHand == INVALID_HANDLE_VALUE) { log(LOG_DBG, "Error %d on create STDERR pipe\n", GetLastError()); } runProcess(); log(LOG_DBG, "After runProcess, new PID is %d/%x\n", piProcInfo.dwProcessId, piProcInfo.dwProcessId); if (piProcInfo.dwProcessId == 0) { log(LOG_DBG, "runProcess failed, closing child STDIN/STDERR\n"); closeChildPipes(); #define FAIL_MSG "Child process failed to start\n" writeChildOutFile(FAIL_MSG, strlen(FAIL_MSG) ); CloseHandle(hChildOut); } else { log(LOG_DBG, "Child process created, setting up for redir/restart/termination\n"); issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail); //log(LOG_DBG, "After read set on STDOUT\n"); issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail); //log(LOG_DBG, "After read set on STDERR\n"); HANDLE harr[4]; for(;;) { harr[0] = hev; harr[1] = piProcInfo.hProcess; harr[2] = stdOutEvt; harr[3] = stdErrEvt; DWORD waitRc2 = WaitForMultipleObjects(4, harr, FALSE, 500); #if 0 if (waitRc2 == -1) { log(LOG_DBG, "Wait error %d\n", GetLastError()); Sleep(500); } log(LOG_DBG, "waitRc2 %d\n", waitRc2); #endif if ((waitRc2 - WAIT_OBJECT_0) == 0) { log(LOG_DBG, "Woke up because another trigger command was received\n"); #define NEW_CMD_MSG "Child process is being terminated because new trigger received\n" writeChildOutFile(NEW_CMD_MSG, strlen(NEW_CMD_MSG)); terminateChild(); CloseHandle(hChildOut); manual_triggered = 1; break; } else if ((waitRc2 - WAIT_OBJECT_0) == 1) { //log(LOG_DBG, "Woke up because child has terminated\n"); closeChildPipes(); #define NORM_MSG "Normal child process termination\n" writeChildOutFile(NORM_MSG, strlen(NORM_MSG)); CloseHandle(hChildOut); break; } else if ((waitRc2 - WAIT_OBJECT_0) == 2) { //log(LOG_DBG, "Woke up because child has stdout\n"); if (GetOverlappedResult(gStdOutReadHand, &stdOutOvl, &bytesRead, TRUE)) { writeChildOutFile(stdOutBuff, bytesRead); ResetEvent(stdOutEvt); issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail); } } else if ((waitRc2 - WAIT_OBJECT_0) == 3) { //log(LOG_DBG, "Woke up because child has stderr\n"); if (GetOverlappedResult(gStdErrReadHand, &stdErrOvl, &bytesRead, TRUE)) { writeChildOutFile(stdErrBuff, bytesRead); ResetEvent(stdErrEvt); issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail); } } else { if (gShuttingDown) { log(LOG_DBG, "Woke with active child and service is terminating\n"); #define SHUTDOWN_MSG "Child process is being terminated because the service is shutting down\n" writeChildOutFile(SHUTDOWN_MSG, strlen(SHUTDOWN_MSG)); terminateChild(); CloseHandle(hChildOut); break; } } if (gShuttingDown) { break; } } } } else if (gShuttingDown) { break; } CloseHandle(gStdOutReadHand); CloseHandle(gStdErrReadHand); } return 0; } void writeChildOutFile(char *msg, int len) { DWORD bytesWritten; WriteFile(hChildOut, msg, len, &bytesWritten, 0); } void terminateChild(void) { if (piProcInfo.dwProcessId != 0) { TerminateProcess(piProcInfo.hProcess, -1); CloseHandle(piProcInfo.hThread); CloseHandle(piProcInfo.hProcess); closeChildPipes(); } } void closeChildPipes(void) { CloseHandle(g_hChildStd_OUT_Wr); CloseHandle(g_hChildStd_ERR_Wr); } void runProcess(void) { SECURITY_ATTRIBUTES saAttr; // Set the bInheritHandle flag so pipe handles are inherited. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create a pipe for the child process's STDOUT. TCHAR szCmdline[]=TEXT("cmd.exe /CC:\\temp\\RunnerService.bat"); STARTUPINFO siStartInfo; BOOL bSuccess = FALSE; // Set up members of the PROCESS_INFORMATION structure. ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); g_hChildStd_OUT_Wr = CreateFile (STD_OUT_PIPE_NAME, FILE_WRITE_DATA, 0, &saAttr, OPEN_EXISTING, 0, NULL); if (g_hChildStd_OUT_Wr == INVALID_HANDLE_VALUE) { log(LOG_DBG, "Error creating child proc stdout file %d\n", GetLastError()); } g_hChildStd_ERR_Wr = CreateFile (STD_ERR_PIPE_NAME, FILE_WRITE_DATA, 0, &saAttr, OPEN_EXISTING, 0, NULL); if (g_hChildStd_ERR_Wr == INVALID_HANDLE_VALUE) { log(LOG_DBG, "Error creating child proc stderr file %d\n", GetLastError()); } // Set up members of the STARTUPINFO structure. // This structure specifies the STDIN and STDOUT handles for redirection. ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; siStartInfo.hStdError = g_hChildStd_ERR_Wr; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; // Create the child process. bSuccess = CreateProcess(NULL, szCmdline, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited 0, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &siStartInfo, // STARTUPINFO pointer &piProcInfo); // receives PROCESS_INFORMATION } void CreateChildOutFile(void) { SYSTEMTIME st; SECURITY_ATTRIBUTES sa; char fName[_MAX_PATH]; InitSAPtr(&sa); GetLocalTime(&st); sprintf(fName, "C:\\TEMP\\runsvcchild_%02d_%02d_%02d_%04d.out", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); hChildOut = CreateFile(fName, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); } void issueRead(HANDLE hFile, OVERLAPPED *overLapped, char *buf, DWORD *dwRead) { //log(LOG_DBG, "Start of issueRead, hfile %08x, ovl is %08x\n", hFile, overLapped); BOOL brc = ReadFile(hFile, buf, 4096, dwRead, overLapped); if (!brc) { DWORD dwle = GetLastError(); if (dwle != ERROR_IO_PENDING) { log(LOG_DBG, "Error %d on ReadFile\n", dwle); } } else { // log(LOG_DBG, "Read issued\n"); } }
你的问题微妙的方式是确保你关闭你不需要的管道的末端:
Us Child +------------------+ +---------------+ | | | | | g_hChildStd_IN_Wr----->g_hChildStd_IN_Rd | | | | | | g_hChildStd_OUT_Rd<------g_hChildStd_OUT_Wr | | | | | +------------------+ +---------------+
您的父进程只需要每个管道的一端:
一旦你启动了你的子进程:确保关闭你不再需要的那些管道的末端。
result = CreateProcess(...); //CreateProcess demands that we close these two populated handles when we're done with them. We're done with them. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); /* We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore. We do keep the handle for the *readable* end of the pipe; as we still need to read from it. The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the console app. When the app closes, it will close the pipe, and ReadFile will return code 109 (The pipe has been ended). That's how we'll know the console app is done. (no need to wait on process handles with buggy infinite waits) */ CloseHandle(g_hChildStd_OUT_Wr); g_hChildStd_OUT_Wr = 0; CloseHandle(g_hChildStd_IN_Rd); g_hChildStd_OUT_Wr = 0;
大多数解决方案的常见问题是人们试图等待进程句柄。 这个问题有很多, 主要的一点是,如果你等待孩子终止,孩子将永远不能终止。
如果孩子试图通过管道发送输出,而且你无法等待,那么你并没有清空管道的末端。 最后管道变满了。 当孩子试图写满管道时, WriteFile
调用会等待管道有空间。 因此,孩子的过程永远不会终止。 你已经僵化了一切。
通过简单地从管道读取来正确的解决方案。 一旦子进程终止,它将CloseHandle
它的管道末端。 下一次尝试从管道中读取时,会被告知管道已关闭( ERROR_BROKEN_PIPE
)。 这就是你知道这个过程已经完成,你没有更多的东西可读; 都没有一个危险的MsgWaitForSingleObject,这是很容易错误地使用,并导致你想避免的错误。
String outputText = ""; //Read will return when the buffer is full, or if the pipe on the other end has been broken while (ReadFile(stdOutRead, aBuf, Length(aBuf), &bytesRead, null) outputText = outputText + Copy(aBuf, 1, bytesRead); //ReadFile will either tell us that the pipe has closed, or give us an error DWORD le = GetLastError; //And finally cleanup CloseHandle(g_hChildStd_IN_Wr); CloseHandle(g_hChildStd_OUT_Rd); if (le != ERROR_BROKEN_PIPE) //"The pipe has been ended." RaiseLastOSError(le);