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.