Adding Console I/O to a Win32 GUI App Windows Developer Journal, December 1997
One of the common misconceptions surrounding Win32 programming is that you must decide early in the design process whether your application will be a console or GUI application. In reality, console applications can create windows and have a message loop just like a GUI application (see "Adding Graphics to Console Applications" by Michael Covington, Visual Developer, June/July 1997). Alternatively, graphical applications can create and use a console. Although Win32 provides functions for communicating with a console, they are a little clumsy to use and require parameters that are unnecessary for simple text I/O. This article shows how to use standard C/C++ I/O with consoles, and how to work around specific problems in the Visual C++ I/O library. Win32 Handles versus RTL Handles The standard C I/O package operates on top of whatever I/O services the local operating system provides. Your C program then manipulates handles of type FILE*, no matter what the actual details of the physical file I/O are. The file I/O package that Win32 provides uses handles of type HANDLE, so Win32 C implementations of the standard I/O functions must internally associate a HANDLE with each FILE*, and eventually call the Win32 API to perform the I/O. C predefines the meaning of three handles: stdin, stdout, and stderr. These names refer to FILE* handles that refer to standard input, output, and error output. Win32 provides a similar concept; it provides a function called GetStdHandle(), and you can pass that function one of three constants (STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, or STD_ERROR_HANDLE) to obtain the HANDLE associated with standard input, standard output, or the standard error output. Exactly what these handles refer to depends on how your Win32 program was built and marked. These handles could be redirected to files, or they could be associated with a console window. If you launch a program from a DOS box (which is basically a console window), it may end up using that DOS box as its console window by default (that is, writing to standard output produces output in the DOS box window). Win32 GUI applications do not, by default, have an associated console window. However, it is possible for your GUI application to explicitly create a console window and then manually associate standard C I/O handles (FILE*) with the appropriate Win32 handles (HANDLE) so that ordinary C runtime functions can operate on the console. To associate a C file handle with a Win32 file handle, the Visual C++ runtime library provides _open_osfhandle(). Given a valid Win32 handle, this function returns an RTL handle that internally operates on the given Win32 handle. This lets you transform the three console handles into compatible C file handles that you can use to redirect stdin, stdout, and stderr to the console. C++ I/O Once stdin, stdout, and stderr are redirected, standard C functions such as printf() and gets() can be used, without change, to communicate with the Win32 console. But what about C++ I/O streams? Since cin, cout, cerr, and clog are closely tied to C’s stdin, stdout, and stderr, you would expect them to behave similarly. This is half right. C++ I/O streams actually come in two flavors: template and non- template. The older non-template version of I/O streams is slowly being replaced by a newer template style of streams first defined by the Standard Template Library (STL) and which are now being absorbed into the ANSI C++ standard. Visual C++ v5 provides both types and allows you to choose between the two by including different header files. STL I/O streams work as you would expect, automatically using any newly redirected stdio handles. Non-template I/O streams, however, do not work as expected. To discover why, I looked at the source code, conveniently provided on the Visual C++ CD-ROM. The problem is that the older I/O streams were designed to use UNIX-style "file descriptors," where integers are used instead of handles (0 for stdin, 1 for stdout, and so on). That’s convenient for UNIX implementations, but Win32 C compilers have to provide yet another I/O layer to represent that style of I/O, since Win32 does not provide a compatible set of functions. In any case, when you call _open_osfhandle() to associate a new Win32 handle with (for example) stdout, it has no effect on the other layer of I/O code. Hence, file descriptor 1 will continue using the same underlying Win32 handle as before, and sending output to cout will not produce the desired effect. Fortunately, the designers of the original I/O stream package foresaw this problem and provided a clean and useful solution. The base class ios provides a static function, sync_with_stdio(), that causes the library to change its underlying file descriptors to reflect any changes in the standard I/O layer. Though this is not strictly necessary for STL I/O streams, it does no harm and lets me write code that works correctly with either the new or old form of I/O streams. A Redirect Function guicon.cpp (Listing 1) and guicon.h (Listing 2) provide code for redirecting C and C++ I/O to a Win32 console. RedirectIOToConsole() does all the work, creating a console and increasing the number of lines buffered with calls to AllocConsole() and SetConsoleScreenBufferSize() respectively. Each process is allowed only one console, so calling AllocConsole() multiple times will not produce multiple consoles. Increasing the screen buffer size allows you to scroll back and look at a history of up to MAX_CONSOLE_LINES of text. The code then creates a new FILE* handle for STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, and STD_ERROR_HANDLE, and the existing stdin, stdout, and stderr are replaced. setvbuf() is then called to turn off input and output buffering, effectively flushing the data with each read and write. Last, but not least, a call to ios::sync_with_stdio() ensures that cin, cout, cerr, and clog are associated with the new stdin, stdout, and stderr. The code is wrapped in preprocessor conditionals to allow it to be easily compiled out for release applications. The code in test.cpp (Listing 3) is a sample of using RedirectIOToConsole(). It outputs text using the C stdio and C++ I/O streams and uses a simple integer to test input. Additionally, if you are using STL streams it tests the wide character versions of cin, cout, cerr, and clog. The rest of the code demonstrates how the Visual C++ C runtime debug functions can be used to output useful debugging information to the console. Since Win32 destroys the console when the application exits, I pause for two seconds before exiting so that you can see the last few pieces of output. Overall, having a console associated with a Win32 GUI application is a very useful tool. Though similar debug text output can be achieved using Win32’s OutputDebugString() and DBWin32 (see "A DBWin32 Debugger for Windows" by Andrew Tucker, C/C++ Users Journal, October 1996), this console technique is much more portable between the Win32 API flavors (namely Windows 95 and NT) and it provides a method of input as well as output. Another application might be to use the console as a simple debugger that allows you to print out crucial data structures and inspect and change variables without the use of a full-blown interactive debugger. Finally, for those applications whose user interface makes sense as both a console app and a GUI, a command-line switch could allow the user to choose which is appropriate for their situation — something that is not as readily available when using a GUI from a console application. References Covington, Michael. "Adding Graphics to Console Applications," Visual Developer, June/July 1997. Tucker, Andrew. "A DBWin32 Debugger for Windows," C/C++ Users Journal, October 1996. Andrew Tucker (ast@halcyon.com) is a software engineer in Seatte, WA. His programming interests include compilers, debuggers, and other areas of systems programming.
Listing 1: guicon.cpp -- A console redirection function
#include <windows.h> #include <stdio.h> #include <fcntl.h> #include <io.h> #include <iostream> #include <fstream> #ifndef _USE_OLD_IOSTREAMS using namespace std; #endif // maximum mumber of lines the output console should have static const WORD MAX_CONSOLE_LINES = 500; #ifdef _DEBUG void RedirectIOToConsole() { int hConHandle; long lStdHandle; CONSOLE_SCREEN_BUFFER_INFO coninfo; FILE *fp; // allocate a console for this app AllocConsole(); // set the screen buffer to be big enough to let us scroll text GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); coninfo.dwSize.Y = MAX_CONSOLE_LINES; SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); // redirect unbuffered STDOUT to the console lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); // FILE *_fdopen(int fd, const char *mode ); 基于文件描述符打開一個(gè)C輸入輸出流 fp = _fdopen( hConHandle, "w" ); *stdout = *fp; setvbuf( stdout, NULL, _IONBF, 0 ); // redirect unbuffered STDIN to the console lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "r" ); *stdin = *fp; setvbuf( stdin, NULL, _IONBF, 0 ); // redirect unbuffered STDERR to the console lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "w" ); *stderr = *fp; setvbuf( stderr, NULL, _IONBF, 0 );
// make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog // point to console as well ios::sync_with_stdio(); } #endif //End of File
Listing 2: guicon.h -- Interface to console redirection function #ifndef __GUICON_H__ #define __GUICON_H__ #ifdef _DEBUG void RedirectIOToConsole(); #endif #endif /* End of File */
Listing 3: test.cpp -- Demonstrating console redirection #include <windows.h> #include <iostream> #include <fstream> #include <conio.h> #include <stdio.h> #ifndef _USE_OLD_OSTREAMS using namespace std; #endif #include "guicon.h"
#include <crtdbg.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { #ifdef _DEBUG RedirectIOToConsole(); #endif int iVar; // test stdio fprintf(stdout, "Test output to stdout\n"); fprintf(stderr, "Test output to stderr\n"); fprintf(stdout, "Enter an integer to test stdin: "); scanf("%d", &iVar); printf("You entered %d\n", iVar); //test iostreams cout << "Test output to cout" << endl; cerr << "Test output to cerr" << endl; clog << "Test output to clog" << endl; cout << "Enter an integer to test cin: "; cin >> iVar; cout << "You entered " << iVar << endl; #ifndef _USE_OLD_IOSTREAMS // test wide iostreams wcout << L"Test output to wcout" << endl; wcerr << L"Test output to wcerr" << endl; wclog << L"Test output to wclog" << endl; wcout << L"Enter an integer to test wcin: "; wcin >> iVar; wcout << L"You entered " << iVar << endl; #endif
// test CrtDbg output _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR ); _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR); _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE ); _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR); _RPT0(_CRT_WARN, "This is testing _CRT_WARN output\n"); _RPT0(_CRT_ERROR, "This is testing _CRT_ERROR output\n"); _ASSERT( 0 && "testing _ASSERT" ); _ASSERTE( 0 && "testing _ASSERTE" ); Sleep(2000); return 0; } //End of File |
|