Oct
2

Windows: Part 1 Jon Keon

C++

After getting my system all setup and ready for C++ programming, it was time to start actually having something being displayed to the screen.

In order to do that on a Windows Box, you’re going to need a window.

There are a variety of sites out there that will get you up and running pretty quick with a simple window but eventually you’re going to want to have a robust class that accomplishes all the functionality you need so you never have to write the boiler plate windows code again for your project.

Window Class Goals:

  • Class that encapsulates Microsoft Windows functionality.
  • Allow for Message Based or Real Time interactivity with the Window.
  • Toggle between Full Screen and Windowed Mode
  • Allow for dynamic re-sizing and re- positioning of the window.
  • Allow for Multiple Windows and Proper message dispatching to the right window instance.
  • Map dynamic functionality in response to a Windows Message.
There’s a lot of functionality there and some of it is a bit complex. So in Part 1, I’ll only be addressing (and basically at that) a few of those goals.
I struggled with this quite a bit. In my mind, I know what I want to build and roughly how to build it. But often the all-encompassing solution requires a bit of growth throughout the development. Requirements change and new caveats pop up depending of what features you are implementing. As much as it pains me (for some reason), this is only the first and rather incomplete version of the Window class. As I actually start using it to build things I will add/remove/refactor and hopefully by the end of my project, I’ll have a class that is a robust and complete as I had originally wanted to design.
I find that if you don’t take that hard stance in moving on with something that is less than ideal but works, you’ll be stuck in a endless state of paralysis thinking about all the what-if’s. A great majority of those what-if’s may never come up, and if they do, then you can address it and rethink the design.
That being said let’s take a look at the class:

Window.h

//////////////////////////////////////////////////////////////////////
// Window //
// //
// Description: //
// Author: jkeon //
//////////////////////////////////////////////////////////////////////
#ifndef _WINDOW_H_
#define _WINDOW_H_#define WIN32_LEAN_AND_MEAN//////////////////////////////////////////////////////////////////////
// INCLUDES //////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

#include <Windows.h>
#include <string/TString.h>
#include <util/DebugUtil.h>

//////////////////////////////////////////////////////////////////////
// NAMESPACE /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

namespace Athena {
//////////////////////////////////////////////////////////////////////
// GLOBALS ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
// CLASS DECLARATION /////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

class Window {

//PUBLIC FUNCTIONS
public:
Window();
Window(const Window& other);
virtual ~Window();

bool Init(HINSTANCE* hInstance, tstring* windowClassName, tstring* windowTitleBarName, int x, int y, int width, int height);
void Show(int nCmdShow);
void Run();
void Destroy();

LRESULT CALLBACK LocalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);

//PUBLIC VARIABLES
public:

//PROTECTED FUNCTIONS
protected:

//PROTECTED VARIABLES
protected:

//PRIVATE FUNCTIONS
private:

//PRIVATE VARIABLES
private:
HINSTANCE* __hInstance;
WNDCLASSEX __definition;
HWND __hwnd;
int __x;
int __y;
int __width;
int __height;
tstring* __windowClassName;
bool __realTime;
bool __messagePumpActive;

};

//////////////////////////////////////////////////////////////////////
// STATICS ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

static LRESULT CALLBACK GlobalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
static Window* globalWindow = 0;

}
#endif

Window.cpp

//////////////////////////////////////////////////////////////////////
// INCLUDES //////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

#include "Window.h"

//////////////////////////////////////////////////////////////////////
// NAMESPACE /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

namespace Athena {

//////////////////////////////////////////////////////////////////////
// STATICS ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

//Every Windows Message will hit this function.
LRESULT CALLBACK GlobalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
if (globalWindow != NULL) {
return globalWindow->LocalWndProc(hwnd, msg, wparam, lparam);
}
else {
return DefWindowProc(hwnd, msg, wparam, lparam);
}
}

//////////////////////////////////////////////////////////////////////
// CONSTRUCTORS //////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

Window::Window() {
__hInstance = NULL;
__windowClassName = NULL;
__x = 0;
__y = 0;
__width = 800;
__height = 600;
__realTime = false;
__messagePumpActive = false;
}

Window::Window(const Window& other) {

}

//////////////////////////////////////////////////////////////////////
// DESTRUCTOR ////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

Window::~Window() {

}

//////////////////////////////////////////////////////////////////////
// INITIALIZE ////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

bool Window::Init(HINSTANCE* hInstance, tstring* windowClassName, tstring* windowTitleBarName, int x, int y, int width, int height) {

//Store Instance Variables
__x = x;
__y = y;
__width = width;
__height = height;
__windowClassName = windowClassName;
__hInstance = hInstance;

//TODO: Pull in some of this from Config file
__definition.cbSize = sizeof(WNDCLASSEX);
__definition.cbClsExtra = 0;
__definition.cbWndExtra = 0;
__definition.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
__definition.hCursor = LoadCursor(NULL, IDC_ARROW);
__definition.hIcon = LoadIcon(NULL, IDI_WINLOGO);
__definition.hIconSm = __definition.hIcon;
__definition.hInstance = *__hInstance;
__definition.lpfnWndProc = GlobalWndProc;
__definition.lpszClassName = __windowClassName->c_str();
__definition.lpszMenuName = NULL;
__definition.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;

//Register the Class
if (!RegisterClassEx(&__definition)) {
//If a failure, let us know about it
etrace("Register Class Failed on %s", __windowClassName->c_str());
return false;
}

//Create the window and store the handle
__hwnd = CreateWindow(__windowClassName->c_str(), windowTitleBarName->c_str(), WS_OVERLAPPEDWINDOW, __x, __y, __width, __height, NULL, NULL, *__hInstance, NULL);
if (!__hwnd) {
//If a failure, let us know about it
etrace("Create Window Failed on %s", __windowClassName->c_str());
return false;
}

//Set the static reference
globalWindow = this;

//Simple trace to let us know it's working
itrace("Window Init Properly for %s", __windowClassName->c_str());

return true;
}

//////////////////////////////////////////////////////////////////////
// DESTROY ///////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

void Window::Destroy() {
//Destroy the Window
DestroyWindow(__hwnd);
__hwnd = NULL;

//Unregister the Class
UnregisterClass(__windowClassName->c_str(), *__hInstance);

//NULL out pointers
__hInstance = NULL;
__windowClassName = NULL;
}

//////////////////////////////////////////////////////////////////////
// BODY //////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

void Window::Show(int nCmdShow) {
//So long as we have the handle to the window, show it
if (__hwnd != NULL) {
ShowWindow(__hwnd, nCmdShow);
}
}

void Window::Run() {

MSG msg;
ZeroMemory(&msg, sizeof(MSG));
__messagePumpActive = true;

//If we're running this as a Real Time Window, use PeekMessage so it's non-blocking
if (__realTime) {
while(__messagePumpActive) {
if (PeekMessage(&msg, __hwnd, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT) {
__messagePumpActive = false;
}
}
}
}
//Otherwise use GetMessage which is blocking and will free up CPU cycles for other apps
else {
while(__messagePumpActive) {
GetMessage(&msg, __hwnd, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT) {
__messagePumpActive = false;
}
}
}
}

LRESULT CALLBACK Window::LocalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
switch (msg) {

case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;

case WM_CLOSE:
PostQuitMessage(0);
return 0;
break;

default:
return DefWindowProc(hwnd, msg, wparam, lparam);
}
}

//////////////////////////////////////////////////////////////////////
// GETTERS/SETTERS ///////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

}

Fairly basic but it get’s the job done.

Some good references where I pulled bits and pieces from are:

Window::Window

When we create a new Window, the first thing it does is just set some defaults for the private variables. Nothing special here.

Window::Init

The next function to call is Init which will initialize the window and actually create a Microsoft Window internally and give us the Handle to that Window.
We pass in the Application Instance as well as a class name for our window and the title in the menu bar and the initial position and size of the window. I feel like the API here is a bit too long so I might take the positioning and sizing part out in the future.
Windows need to be created via a definition, so we need to populate ours. Most of this is pretty straightforward if you follow along with MSDN.
The property lpfnWndProc however is pretty important. This function is where Windows Messages will get piped too and it needs to be a static function.
But one of our goals was to be able to have multiple windows and have messages mapped to the correct instance of the window. If we pipe out messages to this static function, all messages will hit all of our windows and we won’t be able to have specific functionality for them. Fortunately there are ways around this as outlined here but it’s a bit complex and I really need to sit down and fully understand it before I implement it.
After that we register  and then create the window, storing the handle to the actual window in our class.
The final step is registering this window as a static window so our GlobalWndProc function can access this window’s LocalWndProc function. This effectively limits us to one Window though which is definitely not what we want but at this point it will do since we’re only going to have one Window. We will be changing this for sure in the future.

Window::Destroy

The Destroy function is also pretty standard, just tearing things down and cleaning up.

Window::Show

Another standard function, simply lets the outside world show the window. I’m unsure if I’ll keep this. I don’t really want to wrap every little bit of Windows functionality but until I actually start building things with Windows in them, I don’t really know what’s useful or not.

Window::Run

The Run function is the infinite loop which keeps our program running and the window displayed. There are two code paths here (which can be cleaned up)  but they basically serve for whether what’s going on in the Window needs to use the CPU all the time (like a game etc) or whether it’s an application that only needs to respond to specific user interaction.

In Real Time mode, so long as the message pump is active we want to Peek at the Windows Message Stack and see if there are any messages. If there are, we want to Translate that Message and then Dispatch it. The Dispatch Function will send that message to our GlobalWndProc handler and then through the static instance of our window, send it back to our LocalWndProc where we actually handle the message.

When not in Real Time mode, we use GetMessage instead of PeekMessage. GetMessage is a blocking call. So until there is a message in the queue to process, the program will effectively halt at this location, freeing up resources for other programs that are running on your system. If you start interacting with your program, messages will get generated and you can respond to them.

In both cases, we need a way out of the loop so we check if the message is a WM_QUIT message. This means we’re trying to close the application and so we should exit and allow the program to terminate.

This function works just great for a Window but if we ever want our program to Do Something, we need to modify this function so that our custom logic exists inside this while loop or is in direct response to Windows Message. Just another piece we’ll have to modify as we go along.

Window::LocalWndProc

This function is purely for Message Management. Based on the Message that comes through we want to do different things. For now, we’ll only care about a WM_DESTROY or WM_CLOSE. When either of those happen we want to Post  a Quit Message which is what breaks us out of the while loop in Window::Run.

If it’s anything else, we just let the default happen whatever that may be.
There are quite a few messages that can come though and we may not need or want to define behaviours for all of them. Plus, if we want to have two Window classes which do different things on the same message, we’re pretty stuck right now. The plan is to use some sort of mapping to functions so that when a message comes in, we’ll check the instance’s message map to see if there is a function that corresponds to this message. If there is, we’ll call that function. If there isn’t then we’ll just let the default happen.
This will give us the flexibility to define outside the Window class what that window should do in the event of a certain message.

GlobalWndProc

The last function to touch on is pretty simple. We just pipe the function along to our global instances LocalWndProc function. This function will need to change to resolve which instance we want to pipe the function along to since in the future we probably will want multiple windows at a time.

 

 

So there we have it, a basic Window class we can use to display the following on the screen:
Super exciting eh?
Eventually I promise I’ll put something inside the window and actually getting to some interesting topics. For now though, it’s all about the foundations.
Finally for completeness this is my main.cpp to show how to invoke the Window class. Note that the Run() function is blocking because it has the infinite while loop inside it. Until the WM_QUIT message is dispatched we’ll stay in the Run() function and not start destroying or cleaning up our window.

main.cpp

//////////////////////////////////////////////////////////////////////
// INCLUDES //////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////#include <util/DebugUtil.h>
#include <window/Window.h>

//////////////////////////////////////////////////////////////////////
// NAMESPACE /////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
// MAIN //////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////

//Main Entry point to windows application
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

//Create the Main Window
Athena::Window* mainWindow = new Athena::Window();
tstring mainWindowClassName = TEXT("AthenaMainWindowClass");
tstring mainWindowMenuTitle = TEXT("Athena Engine");

//Check that it was created properly. If not we should exit
if(mainWindow == NULL) {
return 0;
}

//Initialize the Main Window
bool success = mainWindow->Init(&hInstance, &mainWindowClassName, &mainWindowMenuTitle, 100, 100, 1920, 1080);
if (success) {
mainWindow->Show(nCmdShow);
mainWindow->Run();
}

//Properly Destroy and cleanup the main window
mainWindow->Destroy();
delete mainWindow;
mainWindow = 0;

//Making it this far means we are finished the program and we should exit normally.
return 0;
}

 

 

This entry was posted on Sunday, October 2nd, 2011 at 7:53 pm and is filed under C++. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.