Write to and Read from the Console - From a GUI Application using the Same cout/cin and printf/scanf






4.82/5 (24 votes)
Adapt console to GUI applications using ConsoleAdapter
Introduction
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.
Attaching a Console
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 theAllocConsole()
API, and replaces the std handles with the help of theReplaceHandles()
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 theFreeConsole()
API. Once this function is invoked, the succeeding in/out calls will fail. In the case of the console created withSpawnConsole()
, 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()
.
Code Snippet
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
.
Using the ConsoleAdapter Demo
- Choose the console type: input, output, or both.
- If you want to attach to the console of the
DumpConsole
provided, just browse the path of the DumpConsole.exe and click the Attach button. You will see a console as shown in the screenshot. - Or you can create a new console by clicking the “Create Console” button.
- You can type the message to be displayed in the console, in the edit box provided to the left of the “Write To Console” button. When the “Write To Console” button is clicked, the message is displayed in the console.
- Click the “Read From Console” button to read an input
string
from the console (you can read any data type, but here I am reading astring
).
So, enjoy using the ConsoleAdapter
. That’s all! Hope that ConsoleAdapter
will be useful for you. Signing off for now.
History
- 6th October, 2006: Initial version