Hello Scene — Win32 Wrangling
My style of software coding involves a lot of quick and dirty prototyping. Sometimes I’m simply checking out the API of some library, other times I’m trying to hash out the details of some routine I myself am writing. Whatever the case, I want to get the boilerplate code out of the way. On Windows, I don’t want to worry about whatever startup code is involved, I just want to put a window on the screen (or not), and start writing my code.
Case in point, I have a project called minwe, wherein I have created a framework for simple apps. One of the common functions to implement in your own code, to get started, is the ‘onLoad()’ function:
#include "apphost.h" void onLoad() { setCanvasSize(320, 240); }
This looks familiar to the way I might write some web page code. The ‘onLoad()’, the introduction of a ‘canvas’. All you have to do is implement this one function, and suddenly you have an application window on the screen. It won’t do much, but it at least deals with mouse and keyboard input, and you can close it to exit the application.
So, what’s the work behind this simplicity, and how do you write something ‘real’? On Windows, there’s a long history of code being written in a simple boilerplate way. You need to know the esoteric APIs to create a Window, running a ‘message loop’, handling myriad system and application defined messages and the like. The classic Windows message loop, for example, looks something like this:
void run() { // Make sure we have all the event handlers connected registerHandlers(); // call the application's 'onLoad()' if it exists if (gOnloadHandler != nullptr) { gOnloadHandler(); } // Do a typical Windows message pump MSG msg; LRESULT res; showAppWindow(); while (true) { // we use peekmessage, so we don't stall on a GetMessage // should probably throw a wait here // WaitForSingleObject BOOL bResult = ::PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE); if (bResult > 0) { // If we see a quit message, it's time to stop the program if (msg.message == WM_QUIT) { break; } res = ::TranslateMessage(&msg); res = ::DispatchMessageA(&msg); } //else { // call onLoop() if it exists if (gOnLoopHandler != nullptr) { gOnLoopHandler(); } } } }
The meat and potatoes of most Windows apps is the Peek/Translate/Dispatch, in an infinite loop. It’s been this way since the beginning of Windows 1.0, and continues to this day. There are tons of frameworks which variously hide this from the programmer, but at the core, it’s still the same.
For my demo scene purposes, I too want to hide this boilerplate, with some enhancements. If you want to follow along, the minwe project contains all that I’m showing here. A common method is to use some C/C++, C# or other library, to encapsulate Windows functions. Then you’re left with a fairly large API in the form of objects that you must learn to manipulate. That’s too hard for me, and requires a large amount of knowledge be used to memorize this API. I want much less than that, but still want all the boilerplate stuff covered.
In the case of ‘onLoad()’, it’s one of those functions that if the user’s code implements it, it will be called. If you don’t implement it, there’s no harm. In a way, you can think of the application shell as being an object, and you are specializing this object by implementing certain functions. If you don’t implement a particular function, the default behavior for that function will be executed. In most cases this simply means nothing will happen.
This is the first bit of magic that minwe implements. This magic is performed using dynamic loading. Dynamic loading simply means I look for a pointer to a function at runtime, rather than at compile time. The crux of this code is as follows:
// // Look for the dynamic routines that will be used // to setup client applications. // Most notable is 'onLoad()' and 'onUnload' // void registerHandlers() { // we're going to look within our own module // to find handler functions. This is because the user's application should // be compiled with the application, so the exported functions should // be attainable using 'GetProcAddress()' HMODULE hInst = ::GetModuleHandleA(NULL); // Start with our default paint message handler gPaintHandler = HandlePaintMessage; // One of the primary handlers the user can specify is 'onPaint'. // If implemented, this function will be called whenever a WM_PAINT message // is seen by the application. WinMSGObserver handler = (WinMSGObserver)::GetProcAddress(hInst, "onPaint"); if (handler != nullptr) { gPaintHandler = handler; } // Get the general app routines // onLoad() gOnloadHandler = (VOIDROUTINE)::GetProcAddress(hInst, "onLoad"); gOnUnloadHandler = (VOIDROUTINE)::GetProcAddress(hInst, "onUnload"); gOnLoopHandler = (VOIDROUTINE)::GetProcAddress(hInst, "onLoop"); }
If you look back at the ‘run()’ function, you see the first function called is ‘registerHandlers()’. When compiling the application, the appmain.cpp file is included as part of the project. This single file contains all the Windows specific bits and magic incantations. Here is usage of the Windows specific GetModuleHandle(), and the real workhorse, ‘GetProcAddress()’. GetProcAddress() is essentially asking the loaded application for a pointer to a function with a specified name. If that function is found within the executable file, the pointer is returned. If the function is not found, then NULL is returned.
typedef void (* VOIDROUTINE)(); static VOIDROUTINE gOnloadHandler = nullptr; gOnloadHandler = (VOIDROUTINE)::GetProcAddress(hInst, "onLoad");
From the top, in classic C/C++ style, if you want to define pointer to a function with a particular signature (parameters and return type), you do that typedef thing. In modern C++ you can do it differently, but this is simple, and you only do it once.
So, the ‘gOnloadHandler’, is a pointer to a function that takes no parameters, and returns nothing. When you look back at the application code, the ‘onLoad()’ function matches this criteria. It’s a function that takes no parameters, and returns nothing. When GetProcAddress() is called, it will find our implementation of ‘onLoad()’, and assign that pointer to our gOnloadHandler variable.
There is one more little bit of magic that makes this work though, and it’s a critical piece. In order for this function to show up in our compiled application as something that can be found using GetProcAddress(), it must be ‘exported’. And thus, in the apphost.h file, you will find:
#define APP_EXPORT __declspec(dllexport) APP_EXPORT void onLoad(); // upon loading application
The #define is there for convenience. The __declspec(dllexport) is the magic that must precede the declaration of the onLoad() function. Without this, the compiler will not make the ‘onLoad()’ name available for the GetProcAddress() to find at runtime. So, even if you implement the function, it will not be found.
If you refer back to the implementation of the ‘run()’ function, you see that right after the function pointers are attempted to be loaded, we try to execute the onSetup() function;
void run() { // Make sure we have all the event handlers connected registerHandlers(); // call the application's 'onLoad()' if it exists if (gOnloadHandler != nullptr) { gOnloadHandler(); }
And during each loop iteration, if the function ‘onLoop()’ has been implemented, that will be called.
This is a very simple and powerful technique. The ability to find a function within an executable has been there from the beginning. It’s perhaps a little known mechanism, but very powerful. With it, we can begin to create a simple shell of a programming environment which feels more modern, and abstracts us far away from the Windows specifics. At this first level of abstraction, there are only three functions the application can implement that get this dynamic loading treatment;
// The various 'onxxx' routines are meant to be implemented by // application environment code. If they are implemented // the runtime will load them in and call them at appropriate times // if they are not implemented, they simply won't be called. APP_EXPORT void onLoad(); // upon loading application APP_EXPORT void onUnload(); APP_EXPORT void onLoop(); // called each time through application main loop
There is ‘onLoad()’, which has been discussed. There is also ‘onUnload()’, which is there for cleaning up anything that needs to be cleaned up before the application closes, and of course, there’s ‘onLoop()’, which is called every time through the main event loop, when we’re not processing other messages.
That’s it for this first installment. I’ve introduced the minwe repository for those who want to follow along with the real code of this little tool kit. I’ve expressed the magic incantations which allow you to create a simple application without worrying about Windows esoterica, and I’ve showed you how you can begin to specialize your application by implementing a couple of key features.
Next time around, I’ll share the functions the runtime provides, discuss that application loop in more detail, talk about putting pixels on the screen, and how to deal with mouse and keyboard.