Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
23 Oct 2006 1  
Adapt console to GUI applications using ConsoleAdapter
Writing to console from a GUI application is a good idea, and it is in fact possible to attach a console to a GUI application. In this article, we will see how that can be done.

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 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().

Sample screenshot

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 a string).

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here