|

Introduction
There are several articles on CodeProject and on MSDN that deal with the redirection of Console process' input/output using pipes. Unfortunately, you might have realized that most of the time, you will receive the output of the program as a giant block of data only when the child process terminates. This is a big issue because usually, you spawn a console process that will perform a task for you, and during the execution time, you want to get a feedback.
This article provides background information on why this problem happens, and a nice solution, easy to integrate in your existing programs.
Background
(If you're not interested in the background explanations, you might want to skip directly to Here comes the solution or to the conclusion).
So you have a nice (third-party) console program that performs a long task and prints out progression messages? Everything seems to work normally when you run that program from the command prompt... But as soon as you want to encapsulate it in a nice GUI program that will present the results to the user, things get worse, and you don't get those progression messages until the end of the subprocess.
Well, I have a good news and a bad news:
- the good news is: it's not the fault of your GUI program.
- the bad news is: most console programs behave differently when their output is redirected to pipes!
Why is that?
Let's dig in Microsoft C Runtime (CRT) Library....
The printf function has an immediate effect when the program is using a real console, and seems delayed when the program is redirected to a pipe.. So, let's take a look at its source code... You will quickly find out that there is a buffering system around the stdout stream, and in order to have _ftbuf flush that stream (= output immediately the result of your printf), you have to have _stbuf reach the last return(1);. Unfortunately, when stdout is redirected to a pipe, you will discover that the if (!_isatty(_fileno(stream))) return(0); prevents this from happening.
The Microsoft CRT considers that stdout is not a TTY when it's a pipe, and changes the buffering behaviour !
So, now there are two options:
- You can add a
fflush(stdout); after each output instruction. This works, but that requires you to have the source code of the console program and to modify/recompile it
- You want a generic solution that works with any console executable. Then follow with me a little further...
But before continuing, it's time to state these:
- This analysis is valid for programs compiled with Microsoft C Runtime Library only.
- It might not be valid with other runtime libraries, but if you see the same symptoms, that means there must be a similar buffering system.
- And finally, the vast majority of console programs out there have been developed with the Microsoft C Runtime Library.
A little deeper in Microsoft C Runtime (CRT) Library....
OK, so, what would be nice is that we cheat the CRT into thinking that stdout is still a TTY when it's a pipe.
Looking at the source code of _isatty, we need the FDEV flag on our file, and this only happens if a call to GetFileType returns FILE_TYPE_CHAR.
Doh'! For a pipe, GetFileType returns FILE_TYPE_PIPE. And MSDN tells us that FILE_TYPE_CHAR is only returned for printers and the console...
So we really need a console... Are we stuck?
Here comes the solution!
My solution is to really use a console buffer that the father process will create, share, and monitor while the child process writes into it.
But there are two drawbacks that we have to solve:
- Output console buffers can only be written to, not read.
- The only way to have a console buffer is to either be a console process or to call
AllocConsole. But we don't want a console window to appear!
The first one is solved by using ReadConsoleOutputCharacter and other console-specific functions that allow us to read information from the console buffer as if we were reading it on the screen.
The second one will be solved in a very elegant way: Instead of calling AllocConsole from our GUI program and quickly find the window in order to hide it (like some articles suggest), we will create an intermediate stub program that our GUI program will spawn instead of spawning the target program.
This little stub program will be a real console process that will be in charge of monitoring the console buffer and flushing the data read onto its own output stream (that our GUI program will redirect to a pipe)
This solution brings two nice advantages:
- The stub program can be run hidden with the
SW_HIDE startup window option, so no console window will be visible.
- If you have already written your GUI program using redirection pipes, you can keep it! The only thing you will have to change is to insert "RTconsole.exe" at the beginning of your
CreateProcess command-line.
A look at the RTconsole source code
The arguments to RTconsole.exe will be the original command-line, including the path to the target console program.
We build an inheritable console screen buffer and fill it with zeroes: SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ|
GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
&sa,CONSOLE_TEXTMODE_BUFFER,NULL);
FillConsoleOutputCharacter(hConsole, '\0',
MAXLONG, origin, &dwDummy);
SetStdHandle(STD_OUTPUT_HANDLE, hConsole);We could have used the standard console that comes with any console program, but this is cleaner and it avoids mixing the subprocess output and our own output in the same console screen buffer. The zeroes will allow us to differentiate with space character outputs. Please note also that CreateConsoleScreenBuffer is possible only because we are a console application. In a GUI application, this would require calling AllocConsole which would display a console popup window.
Now, we start the target process normally, sharing the same console. (RTconsole.exe itself must be started with SW_HIDE to hide the shared console.) PROCESS_INFORMATION pi;
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_FORCEOFFFEEDBACK;
if (!CreateProcess( NULL, commandLine, NULL, NULL,
TRUE, 0, NULL, NULL, &si, &pi))
There seems to be no way to be notified on new characters arriving in the console screen buffer, that's why we now need a monitoring loop: do {
if (WaitForSingleObject(pi.hProcess, 0) != WAIT_TIMEOUT)
exitNow = true;
...
} while (!exitNow);
We exit that loop when the child process has exited, after doing an additional iteration of the loop, for the very last characters output to be taken in account. Monitoring the child process this way also should solve the problem encountered with blocking ReadFile on 16-bit subprocesses.
In the loop, we monitor if the text cursor has moved since the last monitoring, we read the characters on screen from the last known cursor position up to the current position, we fill back with zeroes the portion we have read, and reset the text cursor to its home position. GetConsoleScreenBufferInfo(hConsole, &csbi);
int lineWidth = csbi.dwSize.X;
if ((csbi.dwCursorPosition.X == lastpos.X) &&
(csbi.dwCursorPosition.Y == lastpos.Y))
Sleep(SLEEP_TIME);
else
{
DWORD count = (csbi.dwCursorPosition.Y-lastpos.Y)*
lineWidth+csbi.dwCursorPosition.X-lastpos.X;
LPTSTR buffer = (LPTSTR) LocalAlloc(0, count*sizeof(TCHAR));
ReadConsoleOutputCharacter(hConsole,
buffer, count, lastpos, &count);
FillConsoleOutputCharacter(hConsole, '\0',
count, lastpos, &dwDummy);
...
...
LocalFree(buffer);
}
Then, we analyze the characters read from the screen buffer, and convert them to lines that are written (flushed) to RTconsole's own original output stream.
Weak points
These have been tested to ensure no problem is happening most of the time, but it's always good to know your weak points
Synchronization issues
There is no atomic way of reading the screen buffer and resetting it for the ongoing incoming data. So while we are reading and clearing the screen buffer, there might have been additional characters written by the child process. That's why I'm switching temporarily to THREAD_PRIORITY_TIME_CRITICAL for a quick check to see if the text cursor has moved since then. Characters are not lost in this case because we only clear the characters we have read
Scrolling screen bufferIf the text cursor has not moved, we reset it back to its home position (0,0) to avoid letting it go down the default 25 lines or so of the screen buffer. Otherwise, the screen would start scrolling, and if it happens, then we might lose some text. Note that you could probably use SetConsoleScreenBufferSize to enlarge the screen buffer if the target console program outputs characters too quickly.
Conclusion
I went a bit into details with this article, but remember, in the end:
All you have to do in your GUI application is to insert "RTconsole.exe" at the beginning of the CreateProcess command-line and read the redirected output pipe as usual (see demo program).
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 33 (Total in Forum: 33) (Refresh) | FirstPrevNext |
|
 |
|
|
Great idea,
I've modified it to eliminate the need for synchronization with the child process.
This is the solution:
int main(int argc, char* argv[]) { BOOL bSuccess = FALSE; // get pipe/console to output to HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); DWORD dwDummy;
// parse command line : skip to RTconsole's arguments LPTSTR commandLine = GetCommandLine(); if (*commandLine == '"') commandLine = _tcschr(commandLine+1, _T('"')); else commandLine = _tcspbrk(commandLine, _T(" \t")); if (!commandLine) return -1; commandLine += _tcsspn(commandLine+1, _T(" \t"))+1; if (commandLine[0] == '\0') return -1;
// prepare the console window & inherited screen buffer SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE;
HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,&sa,CONSOLE_TEXTMODE_BUFFER,NULL); if(hConsole == INVALID_HANDLE_VALUE) return -1;
bSuccess = SetConsoleMode(hConsole, ENABLE_WRAP_AT_EOL_OUTPUT); if(!bSuccess) return -1;
COORD dim = GetLargestConsoleWindowSize(hConsole);
bSuccess = SetConsoleScreenBufferSize(hConsole, dim); if(!bSuccess) return -1;
SMALL_RECT sr = {0,0,dim.X-1,dim.Y-1}; bSuccess = SetConsoleWindowInfo(hConsole, TRUE, &sr); if(!bSuccess) return -1;
// fill screen buffer with zeroes bSuccess = FillConsoleOutputCharacter(hConsole, '\0', dim.X * dim.Y, origin, &dwDummy); if(!bSuccess) return -1;
bSuccess = SetStdHandle(STD_OUTPUT_HANDLE, hConsole); // to be inherited by child process if(!bSuccess) return -1;
// start the subprocess PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); STARTUPINFO si; ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_FORCEOFFFEEDBACK; // we don't want the "app starting" cursor
// all other default options are already good : we want subprocess to share the same console and to inherit our STD handles if (!CreateProcess( NULL, commandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { CloseHandle(hConsole); return -2; } CloseHandle(pi.hThread); // always close the hThread after a CreateProcess
// X : columns, Y : rows COORD lastpos = { 0, 0 }; CONSOLE_SCREEN_BUFFER_INFO csbi; short sLimitVert = (dim.Y / 3) * 2; bool exitNow = false; do { if (WaitForSingleObject(pi.hProcess, 0) != WAIT_TIMEOUT) exitNow = true; // exit after this last iteration
// get screen buffer state bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi); if(!bSuccess) break; if ((csbi.dwCursorPosition.X == lastpos.X) && (csbi.dwCursorPosition.Y == lastpos.Y)) Sleep(SLEEP_TIME); // text cursor did not move, sleep a while else { DWORD count = 0;
if(csbi.dwCursorPosition.Y > lastpos.Y) count = dim.X - lastpos.X;
if(csbi.dwCursorPosition.Y >= sLimitVert) { // Until the end of the buffer SetConsoleCursorPosition(hConsole, origin);
count += (dim.Y - lastpos.Y - 1) * dim.X; } else { // Until the cursor if(csbi.dwCursorPosition.Y > lastpos.Y) { count += csbi.dwCursorPosition.X; count += (csbi.dwCursorPosition.Y - lastpos.Y - 1) * dim.X; } else { count += csbi.dwCursorPosition.X - lastpos.X; } }
// read newly output characters starting from last cursor position LPTSTR buffer = (LPTSTR) LocalAlloc(0, count*sizeof(TCHAR)); if(!buffer) break;
bSuccess = ReadConsoleOutputCharacter(hConsole, buffer, count, lastpos, &count); if(!bSuccess) break;
// fill screen buffer with zeroes bSuccess = FillConsoleOutputCharacter(hConsole, '\0', count, lastpos, &dwDummy); if(!bSuccess) break;
// Update lastpos if(csbi.dwCursorPosition.Y >= sLimitVert) lastpos = origin; else lastpos = csbi.dwCursorPosition;
// scan screen buffer and transmit character to real output handle LPTSTR scan = buffer; do { if (*scan) { DWORD len = 1; while (scan[len] && (len < count)) len++;
WriteFile(hOutput, scan, len, &dwDummy, NULL);
scan += len; count -= len; } else { DWORD len = 1; while (!scan[len] && (len < count)) len++;
scan += len; count -= len; } } while (count);
LocalFree(buffer); }
// loop until end of subprocess } while (!exitNow); CloseHandle(hConsole); // release subprocess handle DWORD exitCode; if (!GetExitCodeProcess(pi.hProcess, &exitCode)) exitCode = -3; CloseHandle(pi.hProcess); }
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Your solution does not really attempt to fix the synchronization issue... New characters might have been output by the subprocess between your SetConsoleCursorPosition and ReadConsoleOutputCharacter, overwriting the previous characters (unread yet) And for characters that might have been output between GetConsoleScreenBufferInfo and SetConsoleCursorPosition, your solution might work at the expense of reading all characters up to the end of the screen, and no longer being able to generate empty lines (\r\n) correctly.
Your calculations on "count" appears complex for a result that is equivalent to my single "DWORD count = ..." line. All I see you add is a latency up to 2/3 of the screen buffer before resetting the cursor position, which is worse for subprogram that output quickly a lot of data (because the screen buffer might scroll)
One positive thing in your solution however: The GetLargestConsoleWindowSize seems a good idea to reduce the risk of scrolling.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
1. When performing the operation "SetConsoleCursorPosition" occurs: a. The child process will continue writing at the origin {0,0}. b. We read the characters from "lastpos" until the end of the buffer screen. Thus, any character who has written the child process at the interval [GetConsoleScreenBufferInfo - SetConsoleCursorPosition] will be read. (The reserve of 1 / 3 buffer is precisely for this). c. What the child process writes in the interval [SetConsoleCursorPosition - ...] will be read in the next cycle of reading.
2. My solution works with the console mode "SetConsoleMode(hConsole, ENABLE_WRAP_AT_EOL_OUTPUT)", thus, the characters '\n' are treated as a normal character and are transmitted properly. 3. I don't see any problem.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
rlparra wrote: b. We read the characters from "lastpos" until the end of the buffer screen.
If lastpos is {0,0} (often the case), you might get some characters overwritten in the interval [SetConsoleCursorPosition - ReadConsoleOutputCharacter] before you read them (and, in a lesser extent, erase additional characters than might have been output in the interval [ReadConsoleOutputCharacter - FillConsoleOutputCharacter]
rlparra wrote: 2. My solution works with the console mode "SetConsoleMode(hConsole, ENABLE_WRAP_AT_EOL_OUTPUT)",
oh.. right.. great improvment I didn't understand it would disable the ENABLE_PROCESSED_OUTPUT mode thus threating everything as normal characters. I guess that should even allow for an optimization to stop scanning the read buffer as soon as we encounter a '\0'...
rlparra wrote: (The reserve of 1 / 3 buffer is precisely for this).
Why wait until 2/3 of the buffer is filled before resetting it when we could do this at each loop iteration and be less risky...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I don't understand what you mean, the following is a graphic diagram of operation: Buffer Screen (Schema) I guess a coordinate.
Init CursorPos 2/3 |^000000000000000000000000000000000000000000/000000000000000000000|
Writing 'd' Iter 0: |ddddd^0000000000000000000000000000000000000/000000000000000000000| GetConsoleScreenBufferInfo ReadConsoleOutputCharacter(..., count 5, lastpos 0, ...); FillConsoleOutputCharacter(..., '\0',count 5, lastpos 0, ...); Iter 1: |00000ddddd^00000000000000000000000000000000/000000000000000000000| GetConsoleScreenBufferInfo ReadConsoleOutputCharacter(..., count 5, lastpos 5, ...); FillConsoleOutputCharacter(..., '\0',count 5, lastpos 5, ...); ... Iter N: |00000000000000000000000000000000dddddd^0000/000000000000000000000| GetConsoleScreenBufferInfo ReadConsoleOutputCharacter(..., count 6, lastpos 32, ...); FillConsoleOutputCharacter(..., '\0',count 6, lastpos 32, ...); Iter N+1: |00000000000000000000000000000000000000dddd^/000000000000000000000| GetConsoleScreenBufferInfo "Writing child process 2d" |00000000000000000000000000000000000000dddd^/dd0000000000000000000| SetConsoleCursorPosition(hConsole, {0,0}); |^00000000000000000000000000000000000000dddd/dd0000000000000000000| "Writing child process 2d" |dd^000000000000000000000000000000000000dddd/dd0000000000000000000| ReadConsoleOutputCharacter(..., count 25, lastpos 38, ...); FillConsoleOutputCharacter(..., '\0',count 25, lastpos 38, ...); lastpos = 0 "Writing child process 2d" Iter N+2: |dddd^00000000000000000000000000000000000000/000000000000000000000| GetConsoleScreenBufferInfo ReadConsoleOutputCharacter(..., count 4, lastpos 0, ...); FillConsoleOutputCharacter(..., '\0',count 4, lastpos 0, ...);
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
In fact, your approach resets the cursor to {0,0} less than mine so you're less exposed to the problem I had in mind (see point 1) However you're more exposed to the risk of getting a screen buffer scroll if the child process writes a lot of data while being near the 2/3 limit (see point 2)
Point 1:
|000000000000000000000000000000000000000000^/000000000000000000000| GetConsoleScreenBufferInfo "Writing child process 44d" |dddddddddddddddddddddddddddddddddddddddddd/dd^0000000000000000000| SetConsoleCursorPosition(hConsole, {0,0}); |^dddddddddddddddddddddddddddddddddddddddddd/dd0000000000000000000| "Writing child process 2e" |ee^dddddddddddddddddddddddddddddddddddddddd/dd0000000000000000000| ReadConsoleOutputCharacter(..., count 63, lastpos 0, ...); // lost 2 'd' here
Point 2:
|00000000000000000000000000000000000000^0000/000000000000000000000| GetConsoleScreenBufferInfo "Writing child process 30d" => line scroll // whole screen scrolls up (say 9 characters each line) |00000000000000000000000000000ddddddddddddd/ddddddddddddddddd^0000| // you will read this <========================> SetConsoleCursorPosition(hConsole, {0,0}); ReadConsoleOutputCharacter(..., count 21, lastpos 38, ...);
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Point 1. If the child process is able to write all this in the interval [GetConsoleScreenBufferInfo - ReadConsoleOutputCharacter], then the child process has more priority than the Parent Process.
If this is so, while the parent process sleep 50 milliseconds, the writing child process could exceed the capacity of the buffer in any of the solutions.
Must be taken into account, a display of 800x600 has a buffer screen of 100x48 (4800 characters)
Point 2. I don't understand, if the cursor position is 38, How the child process writes in position 29?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
rlparra wrote: If the child process is able to write all this in the interval [GetConsoleScreenBufferInfo - ReadConsoleOutputCharacter], then the child process has more priority than the Parent Process.
Not necessarily. In Windows preemptive model, processes with lower priority than others are slower but they do get some execution time. This can happen between [GetConsoleScreenBufferInfo - ReadConsoleOutputCharacter], and they can write a big buffer during this time.
rlparra wrote: If this is so, while the parent process sleep 50 milliseconds, the writing child process could exceed the capacity of the buffer in any of the solutions.
That's right.. unfortunately. That's why I made SLEEP_TIME a constant so that in such specific case, it can be adjusted.
rlparra wrote: I don't understand, if the cursor position is 38, How the child process writes in position 29?
Because of the scrolling of the screen buffer that is done by Windows when writing over the end of the console, lines shifting up makes the newly output characters appear up one line in the buffer (1 line = 9 characters in this example)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
In my solution, there is no scrolling, because the screen buffer size is equal to window size, and the cursor never reaches the end of the buffer.
COORD dim = GetLargestConsoleWindowSize(hConsole);
bSuccess = SetConsoleScreenBufferSize(hConsole, dim); if(!bSuccess) return -1;
SMALL_RECT sr = {0,0,dim.X-1,dim.Y-1}; bSuccess = SetConsoleWindowInfo(hConsole, TRUE, &sr);
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
rlparra wrote: In my solution, there is no scrolling, because the screen buffer size is equal to window size
Did you verify this ? In my opinion, it would cause scrolling, on the contrary !
rlparra wrote: the cursor never reaches the end of the buffer.
unless there is a "write" bigger than 1/3 of your screen buffer while being near the 2/3 limit
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I spend a whole day searching for the right answer and read a lot of charlatanes in the web before reaching this.
One comment, std err is out of the loop (still being buffered), it would be a nice addition to the DemoConsole to write to stdout AND stderr and handle it in RTconsole
my 2 cents
-Mat
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The only problem is that there is only one "console" handle, so the best we can do is to direct both stdout & stderr to this console by adding a simple SetStdHandle(STD_ERROR_HANDLE, hConsole); But then the parent process can only poll one stream (the console), and won't be able to differenciate the child stdout/stderr.
I have not tried the alternate solution of creating a second "console" for handling stderr, but I think it won't work gracefully because this second "console" would be visible to the user.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
i have a console app run background(i mean it have a loop: while(true){...}). It is running and i want to "redirect input and output". I don't know do that, plz help me. I searched on internet and all answers are: " ... CreateProcess(...)...". Sorry for my bad english.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
add this code at the start of your child process
// set no buffers for standard streams setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0);
all messages will send without cache
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
mmh i will test that and include it in my article as a replacement for the fflush() option. but when you don't have the source code for the child process' program, then you still need my RTconsole solution
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Fixed the unicode bug as mentioned earlier, also fixed a bug where some of the output characters are missed due to sync issues. Here is the new main():
int _tmain( int argc, _TCHAR* argv[] ) { // get pipe/console to output to HANDLE hOutput = GetStdHandle( STD_OUTPUT_HANDLE ); // parse command line : skip to RTconsole's arguments LPTSTR commandLine = GetCommandLine(); if ( *commandLine == '"' ) commandLine = _tcschr( commandLine + 1, _T('"') ); else commandLine = _tcspbrk( commandLine, _T(" \t") ); if ( ! commandLine ) return -1; commandLine += _tcsspn( commandLine + 1, _T(" \t") ) + 1; if ( commandLine[ 0 ] == '\0') return -1; // prepare the console window & inherited screen buffer SECURITY_ATTRIBUTES sa; sa.nLength = sizeof( SECURITY_ATTRIBUTES ); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; HANDLE hConsole = CreateConsoleScreenBuffer( GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, CONSOLE_TEXTMODE_BUFFER,NULL ); DWORD dwDummy; FillConsoleOutputCharacterA( hConsole, '\0', MAXLONG, origin, &dwDummy ); // fill screen buffer with zeroes SetStdHandle( STD_OUTPUT_HANDLE, hConsole ); // to be inherited by child process // start the subprocess PROCESS_INFORMATION pi; ZeroMemory( &pi, sizeof( PROCESS_INFORMATION ) ); STARTUPINFO si; ZeroMemory( &si, sizeof( STARTUPINFO ) ); si.cb = sizeof( STARTUPINFO ); si.dwFlags = STARTF_FORCEOFFFEEDBACK; // all other default options are already good : we want subprocess to share the same console and to inherit our STD handles if ( ! CreateProcess( NULL, commandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi ) ) { CloseHandle( hConsole ); return -3; } COORD lastpos = { 0, 0 }; CONSOLE_SCREEN_BUFFER_INFO csbi; bool exitNow = false; do { if ( WaitForSingleObject( pi.hProcess, 0 ) != WAIT_TIMEOUT ) exitNow = true; // exit after this last iteration // get screen buffer state GetConsoleScreenBufferInfo( hConsole, &csbi ); int lineWidth = csbi.dwSize.X; if ( ( csbi.dwCursorPosition.X == lastpos.X ) && ( csbi.dwCursorPosition.Y == lastpos.Y ) ) { Sleep( SLEEP_TIME ); // text cursor did not move, sleep a while } else { DWORD count = ( csbi.dwCursorPosition.Y - lastpos.Y ) * lineWidth + csbi.dwCursorPosition.X - lastpos.X; // read newly output characters starting from last cursor position LPBYTE buffer = new BYTE [ count ]; SuspendThread( pi.hThread ); ReadConsoleOutputCharacterA( hConsole, (LPSTR)buffer, count, lastpos, &count ); // fill screen buffer with zeroes FillConsoleOutputCharacterA( hConsole, '\0', count, lastpos, &dwDummy ); lastpos = csbi.dwCursorPosition; GetConsoleScreenBufferInfo( hConsole, &csbi ); if ( ( csbi.dwCursorPosition.X == lastpos.X ) && ( csbi.dwCursorPosition.Y == lastpos.Y ) ) { // text cursor did not move since this treatment, hurry to reset it to home SetConsoleCursorPosition( hConsole, origin ); lastpos = origin; } ResumeThread( pi.hThread ); LPSTR scan = (LPSTR)buffer;
// scan screen buffer and transmit character to real output handle do { if ( *scan ) { DWORD len = 1; while ( scan[ len ] && ( len < count ) ) len++; #ifdef UNICODE // Extract and null terminate then convert to UNICODE and write to file ( excluding the NULL ) LPWSTR UniStr = new WCHAR [ len + 1 ]; UniStr[ len ] = 0;
MultiByteToWideChar( CP_OEMCP, 0, scan, len, UniStr, len );
WriteFile( hOutput, UniStr, len * 2, &dwDummy, NULL );
delete [] UniStr; #else // no need to convert, just write the bytes directly WriteFile( hOutput, scan, len, &dwDummy, NULL ); #endif // UNICODE
scan += len; count -= len; } else { DWORD len = 1; while ( ! scan[ len ] && ( len < count ) ) len++; scan += len; count -= len; len = ( len + lineWidth - 1 ) / lineWidth; for ( ; len ; len-- ) WriteFile( hOutput, _T("\r\n"), 2 * sizeof( TCHAR ), &dwDummy, NULL ); } } while ( count );
delete [] buffer; } // loop until end of subprocess } while ( ! exitNow ); // release subprocess handle DWORD exitCode; if ( ! GetExitCodeProcess( pi.hProcess, &exitCode ) ) exitCode = -3; CloseHandle( hConsole ); CloseHandle( pi.hProcess ); return exitCode; }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
This is just what I was looking for. Would you mind if I include the source for the RTconsole within an article I shall be writing in the upcoming weeks? ( Giving you full credit of course ).
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Thankyou. Just a note for any future codes you wish to share, how about you add a disclaimer/copyright or what not at the top of your sources. I will add something to the top of this one ( though I changed the code slightly ). That way people will know right away it belongs to you instead of the credit going to others
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I thought I did but obviously I forgot I will add the following license:
Copyright (c) 2006-2007 Olivier Marcoux
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Looks good, I will add it.
BTW. I have found a bug in the code. If the build for RTconsole is set to unicode, a few problems appear.
ReadConsoleOutputCharacter() co | | | | | |