![]() |
Desktop Development »
Dialogs and Windows »
Console Programming
Intermediate
License: The Code Project Open License (CPOL)
Writing to and read from the console - From a GUI application using the same cout/cin and printf/scanfBy Sudheesh.P.SAdapt console to GUI applications using ConsoleAdapter. |
VC6WinXP, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
Hi folks, let me start with an interesting problem. I was developing an MFC dialog based application, and now I wanted to write some custom code in the OnPaint() function. Done! Unfortunately, in the first run, my application was blown up. I found that the problem was in the OnPaint() function. What next? Debug. Have you ever tried to debug the OnPaint() function? If not, try it yourself. If yes? I could see you scratching your head. Yes, it is very difficult to debug the OnPaint() or functions like OnMouseMove(). If we want to watch the value of a variable in these functions, the only way is to set up a remote debugger, but it needs some setup and an additional machine.
One other handy approach is to use the OutputDebugString() API to trace the value of a particular variable and monitor it with the help of DebugView (from SysInternals). The next thing I want to say is pretty interesting. Have you ever thought of writing to console from a GUI application? A good idea, right? Yes, it is possible to attach a console to a GUI application.
We can use the standard Windows APIs such as AttachConsole() or AllocConsole() for attaching a console to our application. The difference between these two alternatives is that, the first needs a separate console application to which we can attach the current process. The AttachConsole() API has a process identifier as its parameter. So, we need the PID of the console application to which we need to attach. The next one, AllocConsole(), creates a new console for us and attaches the same to our process. It doesn’t need any argument, nor does it return any value. If succeeded, we can see a new console window waiting for input/output commands.
So far, every thing is fine! The next step is we have to issue the input/output commands to the attached console. We can retrieve the handles to the STDIN and STDOUT using the GetStdHandle() API. With the help of the ReadConsole/WriteConsole APIs, we can read from or write to the console. See the prototypes of these APIs in the MSDN.
BOOL ReadConsole( HANDLE hConsoleInput, // handle to console input buffer LPVOID lpBuffer, // data buffer DWORD nNumberOfCharsToRead, // number of characters to read LPDWORD lpNumberOfCharsRead, // number of characters read LPVOID lpReserved // reserved ); BOOL WriteConsole( HANDLE hConsoleOutput, // handle to screen buffer CONST VOID *lpBuffer, // write buffer DWORD nNumberOfCharsToWrite, // number of characters to write LPDWORD lpNumberOfCharsWritten, // number of characters written LPVOID lpReserved // reserved );
Frankly speaking, I don’t feel comfortable arranging the long set of parameters for each input/output command. It will be better if I could use the same cin, cout, scanf, printf commands when dealing with a console. But unfortunately, though you have successfully allocated a console (using AttachConsole or AllocConsole), the cin and cout or scanf and printf commands fail. Better you try it yourself. If we debug into and watch the contents of the stdin and stout (it is actually &_iobuf[0] and &_iobuf[1], respectively), we can see that the _file member of both are not initialized (-1). They should be normally 0 and 1, respectively. In order to use functions of the standard library, we have to initialize stdin and stout with the handle values of the newly allocated console. With the help of GetStdHandle(STD_INPUT_HANDLE), the handle to the standard input of the newly allocated console can be retrieved. We can get a file handle to STDIN with the _open_osfhandle() API. The _fdopen() API returns a FILE pointer to an already open stream (fpStdIn). Next, we have to replace stdin with the new FILE pointer.
*stdin = fpStdIn;
Similarly,
*stdout = fpStdOut;
This works fine for the scanf and printf functions. But, if before allocating a console, a cin or cout call is invoked, the subsequent calls after allocating the console will fail. The solution is to empty cin and cout just after allocating the console.
I have wrapped whatsoever we talked about into a simple class, ConsoleAdapter. It has mainly three functions:
CreateConsole – Allocates a new console with the help of the AllocConsole() API, and replaces the std handles with the help of the ReplaceHandles() function. SpawnDumpConsole – Attaches to a new dump console specified as a parameter; a sample dummy console implementation is provided along with. DestroyConsole – Detaches the console with the help of the FreeConsole() API. Once this function is invoked, the succeeding in/out calls will fail. In the case of the console created with SpawnConsole(), the spawned console application will be terminated. While creating a ConsoleAdapter instance, you can specify whether the console needs to be automatically freed when the instance is deleted. By turning auto delete off, you have to invoke DestroyConsole() explicitly to destroy the console. The CreateConsole() and SpawnDumpConsole() accept a console type as an argument, i.e., you can specify an input, output, or both modes of the console (INPUT_CONS, OUTPUT_CONS, BOTH).
Only one console can be attached to a process at a time. So, you can use only any one of CreateConsole() or SpawnDumpConsole() at a time.
I have provided a sample dump console application whose path can be specified as a parameter for SpawnDumpConsole().
bool ConsoleAdapter::CreateConsole( CONSOLETYPE_e eConsoleType ) { try { m_eConsoleType = eConsoleType; AllocConsole(); return ReplaceHandles(); } catch ( ... ) { return false; } } bool ConsoleAdapter::SpawnDumpConsole( LPCTSTR lpctszDumConsoleApp, CONSOLETYPE_e eConsoleType ) { try { m_eConsoleType = eConsoleType; STARTUPINFO stStartUpInfo = {0}; PROCESS_INFORMATION stProcInfo = {0}; if(!CreateProcess( lpctszDumConsoleApp,0,0,0,TRUE, CREATE_NEW_CONSOLE, 0,0,&stStartUpInfo, &stProcInfo )) { return false; } m_hDumpConsole = stProcInfo.hProcess; // Waiting for the child process to be initialized Sleep( 100 ); if(!AttachConsole( stProcInfo.dwProcessId )) { return false; } ReplaceHandles(); } catch ( ... ) { return false; } return true; }
You need to put a small delay after CreateProcess(). This is because CreateProcess() returns TRUE before the child process is completely initialized.
bool ConsoleAdapter::ReplaceHandles() { try { if( ( INPUT_CONS == m_eConsoleType ) || ( BOTH == m_eConsoleType ) ) { m_nCRTIn= _open_osfhandle( (long) GetStdHandle(STD_INPUT_HANDLE), _O_TEXT ); if( -1 == m_nCRTIn ) { return false; } m_fpCRTIn = _fdopen( m_nCRTIn, "r" ); if( !m_fpCRTIn ) { return false; } m_fOldStdIn = *stdin; *stdin = *m_fpCRTIn; // if clear is not done, any cout // statement before AllocConsole // will cause, the cin after // AllocConsole to fail, so very important std::cin.clear(); } if( ( OUTPUT_CONS == m_eConsoleType ) || ( BOTH == m_eConsoleType ) ) { m_nCRTOut= _open_osfhandle( (long) GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT ); if( -1 == m_nCRTOut ) { return false; } m_fpCRTOut = _fdopen( m_nCRTOut, "w" ); if( !m_fpCRTOut ) { return false; } m_fOldStdOut = *stdout; *stdout = *m_fpCRTOut; // if clear is not done, any cout // statement before AllocConsole // will cause, the cout after // AllocConsole to fail, so very important std::cout.clear(); } } catch ( ... ) { return false; } return true; }
The sample application provided demonstrates how you can use the ConsoleAdapter.
So, enjoy using the ConsoleAdapter. That’s all! Hope that ConsoleAdapter will be useful for you. Signing off for now.
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 23 Oct 2006 Editor: Smitha Vijayan |
Copyright 2006 by Sudheesh.P.S Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |