2

I am trying to execute a command line program and get the output using C++ on windows. I had implemented this initially with _popen() but this cause the command prompt window to pop-up as mentioned here and here.

As such I implemented a mixture of this answer and this article but encountered an issue with ReadFile() deadlocking. After more reading I added a call to PeakNamedPipe() which in causes the read loop to execute infinity rather than deadlock (the opposite of what I want, I would rather wait for the exe to exit), which lead me to believe that I am just setting this up incorrectly.

The following code is slightly edited from the original (names and such)

std::wstring args = "cmdApplication.exe with some args";
HANDLE readHandle;
HANDLE writeOutHandle;
HANDLE writeErrHandle;
HANDLE writeHandle;

if (!CreatePipe(&readHandle, &writeHandle, NULL, 0)) {
    return false;
}

DuplicateHandle(GetCurrentProcess(), writeHandle, GetCurrentProcess(), &writeOutHandle, 0, true, DUPLICATE_SAME_ACCESS);
DuplicateHandle(GetCurrentProcess(), writeHandle, GetCurrentProcess(), &writeErrHandle, 0, true, DUPLICATE_SAME_ACCESS);

PROCESS_INFORMATION processInfo; 
STARTUPINFO startupInfo;
BOOL success = FALSE; 

ZeroMemory( &processInfo, sizeof(PROCESS_INFORMATION) );

ZeroMemory( &startupInfo, sizeof(STARTUPINFO) );
startupInfo.cb = sizeof(STARTUPINFO); 
startupInfo.hStdError = writeErrHandle;
startupInfo.hStdOutput = writeOutHandle;
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startupInfo.dwFlags |= STARTF_USESTDHANDLES;

success = CreateProcess(NULL, (LPWSTR)args.c_str(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &startupInfo, &processInfo);

DWORD read; 
CHAR buffer[4096];

while(true) {
    success = ReadFile(writeOutHandle, buffer, 4096, &read, NULL);//return false, GetLastError() returns ERROR_BROKEN_PIPE
    if( ! success || read == 0 ) break;
}

CloseHandle(readHandle);

//Deal with the data in buffer

Can I run a command line application like this or I should I run cmd.exe and use and input pipe to provide the args? Have I actually set up my pipes correctly?

Edit: After some back and forth in the comments, ReadFile() no longer deadlocks (keeping the write handle was preventing the child from exiting fully) but unfortunately returns false with error 109: ERROR_BROKEN_PIPE most likely related to this article (see exert below).

If an anonymous pipe is being used and the write handle has been closed, when ReadFile attempts to read using the pipe's corresponding read handle, the function returns FALSE and GetLastError returns ERROR_BROKEN_PIPE.

Which begs the question, if I can't read while the pipe is open (due to ReadFile() blocking) and I can't read when it exits (as I get an error saying the pipe has been ended), how do I read from the pipe?

Community
  • 1
  • 1
Ian Holstead
  • 121
  • 9
  • 1
    You've piped the child's standard output into its own standard input; that might or might not matter, depending on exactly what the child does, but it's not a good idea. Create two separate pipes, one for standard input and one for standard output. To make the loop behave the way you want it to (i.e., to detect when the child has exited) you will need to close your copy of the child's standard output handle *after* launching the child but *before* entering the loop. (Windows can't close the pipe, because although the child has exited, *you* still have a handle you could use to write to it.) – Harry Johnston Apr 23 '15 at 21:01
  • You have odd settings for the console window. `wShowWindow = SW_HIDE` says to hide the window if it exists. But `CREATE_NO_WINDOW` says to attach to a new console that has no window. But this is overruled by `DETACHED_PROCESS`, which says to not even attach to a console. – Eryk Sun Apr 24 '15 at 14:19
  • @HarryJohnston That's definitely not what I want! Could you point out where I'm piping one to the other? I'm not totally sure what you mean about closing the handle. Don't I want the output handle to get the output? – Ian Holstead Apr 24 '15 at 14:57
  • @eryksun Thanks for the tip, those flags were doing me no favours and as such I have removed them, tested and updated the question (unfortunately it didn't fix (all of) the problem). – Ian Holstead Apr 24 '15 at 14:57
  • Your process initially has handles to both the read and write pipe handles and the child process also inherits both handles. Don't let the child inherit the read handle. Pass `NULL` for `lpPipeAttributes`. Then call `DuplicateHandle` twice on the write handle to create two inheritable handles for `hStdOutput` and `hStdError`. After creating the process, close the write handles in your process. That way when the child's write handles close (e.g. due to `ExitProcess`), the reference count for the write side of the pipe object goes to 0 and it can really close. – Eryk Sun Apr 24 '15 at 16:39
  • @eryksun when you say 'close the write handles in your process' do you mean the parent or the child? I can't edit the child process. I have updated with your other recommendations. It no longer deadlocks, but it ReadFile fails too... – Ian Holstead Apr 24 '15 at 20:09
  • 1
    I meant the 3 write handles in the parent. Once the child is created there's no reason to keep them open in the parent. They'll just keep the pipe from closing. `ReadFile` on the read handle will block if you don't close those write handles. Also, don't set the child's `hStdInput` to the pipe's `readHandle`. You don't want the child to see its own output on its standard input. Create another pipe if you need to write to the child's standard input. Otherwise you can duplicate the current standard input handle (from `GetStdHandle`) to set as `hStdInput`. – Eryk Sun Apr 24 '15 at 20:30
  • @eryksun That makes a lot of sense, Thanks! I have once again updated the question, hopefully for the last time. – Ian Holstead Apr 27 '15 at 21:18
  • ERROR_BROKEN_PIPE is normal, but you shouldn't be seeing it until *after* you've read whatever data the child sent. Note that the code as currently shown in the question discards whatever data it receives, looping until the read fails, so perhaps that is your problem? – Harry Johnston Apr 27 '15 at 22:21
  • 1
    Also, the code currently shown calls CreateProcess with handle inheritance disabled. It must be enabled in order for the child to inherit the pipe handles. That would certainly explain why the pipe is breaking immediately. – Harry Johnston Apr 27 '15 at 22:23
  • @HarryJohnston, I missed that `FALSE` was being passed for `bInheritHandles` this entire time. Nice catch. – Eryk Sun Apr 27 '15 at 22:33
  • @HarryJohnston that's it! Thanks! Do either of you or erykson feel like posting it as an answer? – Ian Holstead Apr 28 '15 at 14:52
  • 1
    Glad to hear that helped. There's an older question that covers this, so I'm voting to close as a duplicate rather than re-posting my existing answer. :-) – Harry Johnston Apr 28 '15 at 21:31

0 Answers0