How can I read the output of a child process?

I wrote a function that tries to read the output of the command line of a child process through a pipe. This should be a simple subset of MSDN. Creating a child process with a redirected input and output article , but I am clearly making a mistake.

ReadFile (...) calls blocks forever, regardless of whether I place it before or after a call to WaitForSingleObject (...), which should signal the completion of the child process.

I read all the answers that suggest "Use asynchronous ReadFile" and I am open to this suggestion if someone can give me an idea of ​​how this is done on the pipe. Although I do not understand why asynchronous I / O is needed for this case.

#include "stdafx.h" #include <string> #include <windows.h> unsigned int launch( const std::string & cmdline ); int _tmain(int argc, _TCHAR* argv[]) { launch( std::string("C:/windows/system32/help.exe") ); return 0; } void print_error( unsigned int err ) { char* msg = NULL; FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL ); std::cout << "------ Begin Error Msg ------" << std::endl; std::cout << msg << std::endl; std::cout << "------ End Error Msg ------" << std::endl; LocalFree( msg ); } unsigned int launch( const std::string & cmdline ) { TCHAR cl[_MAX_PATH*sizeof(TCHAR)]; memset( cl, 0, sizeof(cl) ); cmdline.copy( cl, (_MAX_PATH*sizeof(TCHAR)) - 1); HANDLE stdoutReadHandle = NULL; HANDLE stdoutWriteHandle = NULL; SECURITY_ATTRIBUTES saAttr; memset( &saAttr, 0, sizeof(saAttr) ); saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create a pipe for the child process STDOUT. if ( ! CreatePipe(&stdoutReadHandle, &stdoutWriteHandle, &saAttr, 5000) ) throw std::runtime_error( "StdoutRd CreatePipe" ); // Ensure the read handle to the pipe for STDOUT is not inherited. if ( ! SetHandleInformation(stdoutReadHandle, HANDLE_FLAG_INHERIT, 0) ) throw std::runtime_error( "Stdout SetHandleInformation" ); STARTUPINFO startupInfo; memset( &startupInfo, 0, sizeof(startupInfo) ); startupInfo.cb = sizeof(startupInfo); startupInfo.hStdError = stdoutWriteHandle; startupInfo.hStdOutput = stdoutWriteHandle; startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startupInfo.dwFlags |= STARTF_USESTDHANDLES; char* rawEnvVars = GetEnvironmentStringsA(); //__asm _emit 0xcc; PROCESS_INFORMATION processInfo; memset( &processInfo, 0, sizeof(processInfo) ); std::cout << "Start [" << cmdline << "]" << std::endl; if ( CreateProcessA( 0, &cl[0], 0, 0, false, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, rawEnvVars, 0, &startupInfo, &processInfo ) ) { //CloseHandle( stdoutWriteHandle ); DWORD wordsRead; char tBuf[257] = {'\0'}; bool success = true; std::string outBuf(""); unsigned int t; while(success) { //__asm _emit 0xcc; std::cout << "Just before ReadFile(...)" << std::endl; success = ReadFile( stdoutReadHandle, tBuf, 256, &wordsRead, NULL); (t=GetLastError())?print_error(t):t=t; std::cout << "Just after ReadFile(...) | read " << wordsRead<< std::endl; std::cout << "."; if( success == false ) break; outBuf += tBuf; tBuf[0] = '\0'; } std::cout << "output = [" << outBuf << "]" << std::endl; if ( WaitForSingleObject( processInfo.hProcess, INFINITE ) == WAIT_OBJECT_0 ) { unsigned int exitcode = 0; GetExitCodeProcess( processInfo.hProcess, (LPDWORD)&exitcode ); std::cout << "exitcode = [" << exitcode << "]" << std::endl; //__asm _emit 0xcc; CloseHandle( processInfo.hProcess ); CloseHandle( processInfo.hThread ); return exitcode; } } else { DWORD procErr = GetLastError(); std::cout << "FAILED TO CREATE PROCESS!" << std::endl; print_error( procErr ); } return -1; } // end launch() 
+6
source share
2 answers

There are several errors in the code, but the most important thing is that you specified FALSE for the bInheritHandles argument to CreateProcess . A new process cannot use this channel if it does not inherit it. In order for the descriptor to be inherited, the argument bInheritHandles must be TRUE , and the descriptor must have inheritance.

Other questions:

  • You specify CREATE_UNICODE_ENVIRONMENT , but pass the ANSI environment block. Note that passing NULL to lpEnvironment easier, and the system will copy the environment block for you. In this case, you do not need to specify CREATE_UNICODE_ENVIRONMENT, even if you are compiling in Unicode mode.

  • Similarly, if you call CreateProcessA, you must use STARTUPINFOA.

  • You do not terminate tBuf zero every time around the loop, so you will get extra characters in your output buffer.

  • You need to close stdoutWriteHandle before entering a read loop, or you won’t know when the subprocess will exit. (Or you can use asynchronous I / O and explicitly check the output of the process.)

  • GetLastError() undefined if the API function completed successfully, so you should only call it if ReadFile returns FALSE . (Of course, in this case it is purely cosmetic, since you are not acting on the error code.)

For reference, here is my revised version of your code. I turned it into regular C (sorry!), Because that is what I am familiar with. I compiled and tested in Unicode mode, but I think that it should also work without changes in ANSI mode.

 #define _WIN32_WINNT _WIN32_WINNT_WIN7 #include <windows.h> #include <stdio.h> void launch(const char * cmdline_in) { PROCESS_INFORMATION processInfo; STARTUPINFOA startupInfo; SECURITY_ATTRIBUTES saAttr; HANDLE stdoutReadHandle = NULL; HANDLE stdoutWriteHandle = NULL; char cmdline[256]; char outbuf[32768]; DWORD bytes_read; char tBuf[257]; DWORD exitcode; strcpy_s(cmdline, sizeof(cmdline), cmdline_in); memset(&saAttr, 0, sizeof(saAttr)); saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create a pipe for the child process STDOUT. if (!CreatePipe(&stdoutReadHandle, &stdoutWriteHandle, &saAttr, 5000)) { printf("CreatePipe: %u\n", GetLastError()); return; } // Ensure the read handle to the pipe for STDOUT is not inherited. if (!SetHandleInformation(stdoutReadHandle, HANDLE_FLAG_INHERIT, 0)) { printf("SetHandleInformation: %u\n", GetLastError()); return; } memset(&startupInfo, 0, sizeof(startupInfo)); startupInfo.cb = sizeof(startupInfo); startupInfo.hStdError = stdoutWriteHandle; startupInfo.hStdOutput = stdoutWriteHandle; startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startupInfo.dwFlags |= STARTF_USESTDHANDLES; // memset(&processInfo, 0, sizeof(processInfo)); // Not actually necessary printf("Starting.\n"); if (!CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, 0, &startupInfo, &processInfo)) { printf("CreateProcessA: %u\n", GetLastError()); return; } CloseHandle(stdoutWriteHandle); strcpy_s(outbuf, sizeof(outbuf), ""); for (;;) { printf("Just before ReadFile(...)\n"); if (!ReadFile(stdoutReadHandle, tBuf, 256, &bytes_read, NULL)) { printf("ReadFile: %u\n", GetLastError()); break; } printf("Just after ReadFile, read %u byte(s)\n", bytes_read); if (bytes_read > 0) { tBuf[bytes_read] = '\0'; strcat_s(outbuf, sizeof(outbuf), tBuf); } } printf("Output: %s\n", outbuf); if (WaitForSingleObject(processInfo.hProcess, INFINITE) != WAIT_OBJECT_0) { printf("WaitForSingleObject: %u\n", GetLastError()); return; } if (!GetExitCodeProcess(processInfo.hProcess, &exitcode)) { printf("GetExitCodeProcess: %u\n", GetLastError()); return; } printf("Exit code: %u\n", exitcode); CloseHandle( processInfo.hProcess ); CloseHandle( processInfo.hThread ); return; } int main(int argc, char** argv) { launch("C:\\windows\\system32\\help.exe"); return 0; } 
+9
source

In ReadFile (), which you set to NULL, there is the parameter "LPOVERLAPPED lpOverlapped". It seems like the only way is to allow overlapping I / O on your channel and then use WaitForSingleObject () for "overlapped.hEvent".

Another way is to use the ConnectNamedPipe function and create an OVERLAPPED structure for the channel.

+1
source

Source: https://habr.com/ru/post/917270/


All Articles