Monday, December 2, 2013

Win32 Console Issues

Console Shutdown Issues

When using a console with your Windows application, it is possible for the user to close the console window using the system menu or clicking the 'x' button in the upper right corner of the console frame.  Doing this will cause the Windows application to terminate.  As far as I can tell, this functions like an application abort and does not give you the opportunity to clean up or gracefully shut down your application.  Worse yet, terminating the application may not be what the user expects. No warning is given or chance to cancel the impending shutdown.

The CtrlHandler Function

Windows does provide a way to hook a control handler chain which theoretically allows you to detect and act upon the close, ctrl-c, ctrl-break, and logoff events.  Unfortunately, there is no way to prevent the events.  Your event handling function can be registered when the Console object init function is run.

SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE );

The event processing function is declared as a static member of the Console object.

static bool CtrlHandler(DWORD fdwCtrlType);

Finally the event processing function is coded as part of the Console definition in Console.cpp.

bool Console::CtrlHandler(DWORD fdwCtrlType)
{
switch(fdwCtrlType) {
case CTRL_CLOSE_EVENT:
//
// do some cleanup
//
return false;
case CTRL_SHUTDOWN_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_LOGOFF_EVENT:
return false;

default:
return false;
}
}

Since the event handler is coded as a static member, it cannot access any non-static function or member unless it first acquires an instance of the console object.  This is a limitation of how the Console object is currently coded.  This is not a problem if a static member is created which is a pointer to the current Console instance.

Console.h

class Console
{
    ...

public:
    static Console* m_pCons;
};

You must create an instance of m_pCons which can then be initialized in the console init function.

Console * m_pCons = 0;

bool Console::init()
{
   m_pcons = this;

   ...
}

Now inside the event handler you can use the Console instance pointer m_pCons to access any of the non-static members of the console object.

Doing this makes m_pCons a global pointer accessible anywhere in the code.  A better way to do this would be to create the console object as a singleton.  Use of the singleton pattern is not without its fair share of controversy in the C++ community. Personally, I see use of singletons as a benefit for amateur or hobbyist type applications, although in the long run, it is probably better to learn from the pros and emulate their methods.

Preventing the Close

Creating the event handler, really accomplishes very little in my opinion.  After you do what you can do accessing the various functions and methods of the Console object or any other global objects, the application will still terminate.  There seems to be no practical way to avoid it once it gets that far.  An alternative would be to prevent the user from accessing the close function in the first place.  As it turns out this is very simple to do.  Simply get the console window handle and use it to get the handle of the system menu.  Then delete the "close" menu.  This eliminates the close selection from the system menu and disables the 'x' button, preventing the user from inadvertently shutting down the application.

bool Console::init()
{
   ...

   if (!AllocConsole())
       return false;

   ...

    m_hCons = GetConsoleWindow();
    if (m_hCons <= 0)
        return false;
    HMENU hmenu = GetSystemMenu(m_hCons, FALSE);
    DeleteMenu(hmenu, SC_CLOSE, MF_BYCOMMAND);

    return true;
}

This works quite well at eliminating the accidental 'close' but it will not prevent the user from shutting down the application using a log off or other kind of termination.  That's okay because it is probably not a good idea to take all control away from the user.

A Better Way?

Considering the programmer has limited control over the console window without hooking deeply into its messaging system, the programmer must understand the vulnerabilities of using a console window and weigh the advantages with the disadvantages.  The console window is a ready-made device built to handle character-mode streams with minimal code.  On the other hand it does provide a way for the user to abort the application with almost no way to prevent it.  Disabling the close menu does provide a method to prevent an inadvertent termination of the application.  If the goal is to use the console window as a logging and messaging device, it makes sense to create it once when the application is started and not shut it down until the application has finished running.  With the console's window handle, it is easy to hide or show the console when necessary from your application without freeing it using the FreeConsole method.

A safer way, perhaps, to deal with logging and messaging is to create your own window designed to display logging text.  Since a window is a graphical device rather than a character-mode device, much more can be done, including adding controls like buttons and checkboxes.  Of course, then the console window is essentially a dialog box but you will have full control over how it is created and when it is destroyed.  I will no doubt revisit this topic in the future.

Sunday, December 1, 2013

Create A Win32 Console

Creating a Win32 Console
Windows applications can often take advantage of a Win32 Console to provide simple character-mode I/O which is useful for displaying debugging information or log messages, and provides a simple way to input commands for the application.  When you create a console process under Visual Studio, the console is created for you.  This happens when your compiled program entry point is a main function.  If the compiled application is a Win32 application with a WinMain entry function, you must create your own console in order to use one.  It is not difficult to do, and once done, provides a convenient way to work with the stdin, stdout and stderr character streams.

The example code Main.cpp is a typical WinMain function.  The function is very simple.  It creates an application object and tells it to run.  When the application is finished running, the WindMain function exits and your program ends.

Main.cpp

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "Application.h"


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
 Application* app = new Application;
 if( app->init(hInstance))
  app->run();
 return 0;
}

The program Application is the master object which creates the functioning parts of the program. It consists of an init function which creates the objects and a shutdown function which deletes the objects. It also has an update function which updates each object.  The run method is an endless loop which continues to repeat as long the update function returns true.  If update returns false, the shutdown function is called and the application exits the run method which returns to WinMain and the program ends.

Application.h

#ifndef __APPLICATION_H__
#define __APPLICATION_H__

#include <windows.h>
#include "Console.h"

class Application
{
public:
 bool init(HINSTANCE hInst);
 void run();
 void shutDown();
 bool update();
 void render();

private:
 void setAppDefaults();

private:
 HINSTANCE m_hInstance;
 Console* con;

private:
};
#endif

For this demo, the application init function creates the application's one and only object; the console.  If console creation is successful, the console will be repeatedly updated by invoking the update function in the application run method.

Application.cpp

#include "Application.h"

bool Application::init(HINSTANCE hInst)
{
 // create application framework
 m_hInstance = hInst;

 // create a console for logging
 con = 0;
 con = new Console;
 if (!con->init())
  return false;
 con->printBanner();

 return true;
}

void Application::shutDown()
{
 if (con) delete con;
}

bool Application::update()
{
 con->update();
 return true;
}

void Application::render()
{
}

void Application::run()
{
 for(;;)
 {
  if ( !update() )
   break;
  render();
 }
 shutDown();
}

Console.h defines the console object, which is really a manager of sorts, responsible for creating the command console, providing methods for interacting with the console and ultimately, cleanly deleting it when it is no longer needed.

Console.h

#ifndef __CONSOLE_H__
#define __CONSOLE_H__

#include <windows.h>
#include <string>
#include <iostream>
#include <stdio.h>


class Console
{
public:
 bool init();
 void shutDown();
 void update();
 void print(char * text, ...);
 void printBanner();

 bool isConsole() {return m_bCons;}

 ~Console() {shutDown();}

protected:
 void command();
    bool errorExit (std::string s);
 void keyEventProc(KEY_EVENT_RECORD ker);
    void mouseEventProc(MOUSE_EVENT_RECORD mer);
 void resizeEventProc(WINDOW_BUFFER_SIZE_RECORD wbsr);

private:
 HWND  m_hCons;    // window handle of console
 bool  m_bCons;    // console created if true
 DWORD  m_dwOldMode;   // old I/O state
    DWORD  m_dwMode;    // current I/O state
 HANDLE  m_hStdIn;    // standard handles
 HANDLE  m_hStdOut;
 HANDLE  m_hStdErr;
 std::string m_CmdBuf;    // buffer for input strings
 FILE*  m_ConOut;    // file stream pointer
};

#endif

The init function creates a new console with Windows AllocConsole function. After this, gathers the standard I/O handles and then redirects stdout and stderr to the console.  The freopen_s function can also redirect stdout or stderr to a file by replacing "CON" with the file specification, such as "app.log".

Console.cpp

#include "console.h"

bool Console::init()
{
 m_bCons = false;

 if ( !AllocConsole() )
  return false;

 m_hStdIn  = GetStdHandle(STD_INPUT_HANDLE);
 m_hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
 m_hStdErr = GetStdHandle(STD_ERROR_HANDLE);

 errno_t err = 0;
 
 err = freopen_s( &m_ConOut, "CON", "w", stdout);
 if (err)
  return errorExit("Redir stdout");
 err = freopen_s( &m_ConOut, "CON", "w", stderr);
 if (err)
  return errorExit("Redir stderr");
 

 //CONSOLE_SCREEN_BUFFER_INFO lpSBI;
 //GetConsoleScreenBufferInfo(m_hStdOut, &lpSBI);
 COORD sz;
 sz.X = 80;
 sz.Y = 1024;
 SetConsoleScreenBufferSize(m_hStdOut, sz);

 if (m_hStdIn == INVALID_HANDLE_VALUE)
        return errorExit((LPSTR)"GetStdHandle");

    m_CmdBuf = "";  // create an empty command buffer

    // Save the current input mode, to be restored on exit.
    if (! GetConsoleMode(m_hStdIn, &m_dwOldMode) )
        return errorExit((LPSTR)"GetConsoleMode");

    m_dwMode = ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT;
    if (! SetConsoleMode(m_hStdIn, m_dwMode) )
        return errorExit((LPSTR)"SetConsoleMode");

 m_hCons = GetConsoleWindow();
 if (m_hCons <= 0)
  return false;
 m_pCons = this;
 return true;
}

Finally, the screen buffer is set to 8 characters by 1024 lines, we get the current input mode, set the console input mode to suit our application, then gather other miscellaneous info about the console we may or may not need later.  The rest of the demo code for the console object is shown below, including the printBanner functions illustrates several different ways, text can be output to the console.

Console.cpp (cont)

bool Console::errorExit(std::string s)
{
 std::cerr<<"**CONSOLE_init FAIL** "<< s << std::endl;
 return false;
}

void Console::print(char *text, ...)
{
 va_list argList;
 va_start(argList, text);

 //Write the text
 vfprintf(stdout, text, argList);
 putc('\n', stdout);

 va_end(argList);
}

void Console::shutDown()
{
 fflush(stdout);
 fflush(stderr);
 ////fclose(m_ConOut);
    SetConsoleMode(m_hStdIn, m_dwOldMode);
 FreeConsole();
}

void Console::update()
{
 // read console events
 //LPDWORD nEvents = 0;
    DWORD nEvents;

    //std::cout << m_hStdIn << std::endl;

 GetNumberOfConsoleInputEvents(m_hStdIn, &nEvents);
 if ( !nEvents )
  return;

 INPUT_RECORD irInBuf[128];
 DWORD   cNumRead;
    if (! ReadConsoleInput(
            m_hStdIn,    // input buffer handle
            irInBuf,     // buffer to read into
            128,         // size of read buffer
            &cNumRead) ) // number of records read
        errorExit("ReadConsoleInput");

    for (DWORD i = 0; i < cNumRead; i++)
    {
        switch(irInBuf[i].EventType)
        {
            case KEY_EVENT: // keyboard input
                keyEventProc(irInBuf[i].Event.KeyEvent);
                break;

            case MOUSE_EVENT: // mouse input
                mouseEventProc(irInBuf[i].Event.MouseEvent);
                break;

            case WINDOW_BUFFER_SIZE_EVENT: // scrn buf. resizing
                resizeEventProc( irInBuf[i].Event.WindowBufferSizeEvent );
                break;

            case FOCUS_EVENT:  // disregard focus events

            case MENU_EVENT:   // disregard menu events
                break;

            default:
                errorExit("Unknown event type");
                break;
        }
    }
}
void Console::keyEventProc(KEY_EVENT_RECORD ker)
{
    char t = '\0';
    if (ker.bKeyDown)
    {
        char t = ker.uChar.AsciiChar;

        if (t == 13)
        {
            std::cout << std::endl;
            // process the command buffer
            command();
            // reset the command buffer
            m_CmdBuf = "";
            return;
        }
        if (t == 8)
        {
            if (!m_CmdBuf.empty())
            {
                m_CmdBuf.resize (m_CmdBuf.size () - 1);
            }
            std::cout<<t;
            std::cout<<" ";
        }
        std::cout<<t;
        if (t>31)
            m_CmdBuf += t;
    }
}

void Console::mouseEventProc(MOUSE_EVENT_RECORD mer)
{
#ifndef MOUSE_HWHEELED
#define MOUSE_HWHEELED 0x0008
#endif
    return;

    printf("Mouse event: ");

    switch(mer.dwEventFlags)
    {
        case 0:

            if (mer.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)
            {
            }
            else if (mer.dwButtonState == RIGHTMOST_BUTTON_PRESSED)
            {
            }
            else
            {
            }
            break;
        case DOUBLE_CLICK:
            break;
        case MOUSE_HWHEELED:
            break;
        case MOUSE_MOVED:
            break;
        case MOUSE_WHEELED:
            break;
        default:
            break;
    }
}

void Console::resizeEventProc(WINDOW_BUFFER_SIZE_RECORD wbsr)
{
}

void Console::printBanner()
{
 printf("+==============================================+\n");
 printf("+           E10 Graphics Framework             +\n");
 printf("+              (c) 2012 - 2013                 +\n");
 printf("+                 J. Kellams                   +\n");
 printf("+==============================================+\n");
 std::cerr << "testing" << std::endl;
}

The command function demonstrates a method for processing user input in the console window.  Entering the command "exit" will close the console window and terminate the application.

void Console::command()
{
 bool result = false;
 
 if (m_CmdBuf == "exit"){
  SendMessage(m_hCons, WM_CLOSE, 0, 0);
  result = true;
 }
 if (result)
  print("Command processed: %s", m_CmdBuf.c_str());
 else
  print("Unknown command: %s", m_CmdBuf.c_str()); 
}


Now, while this code works (or at least appears to) it is far from perfect. In fact, there is a major flaw in the shutdown.  If the user closes the console window from the system menu (clicking the 'X" button, for example) the shutdown method is bypassed and the application is not shutdown in a controlled way.  I will discuss this in more detail in the next post and show some ways to prevent this problem.