Click here to Skip to main content
15,860,861 members
Articles / Programming Languages / C++
Article

Writing Win32 Apps with C++: V2 - part 1

Rate me:
Please Sign up or sign in to vote.
4.70/5 (34 votes)
20 Jun 2005CPOL14 min read 107.3K   1.2K   73   24
An independent framework to handle Win32 objects inside C++ classes.

Contents

Introduction

This is the fourth edition of this article, with a review code, based on the templates presented in "A policy based reference counting", providing classes to make "smart" Win32 objects.

I'll not go in to the introductory disclaimers of the previous article, they'll be the same here. Pure C++ and STL (and –obviously- "windows.h") this time.

Code structure

Same as in the previous article: a collection of headers whose dependency are solved in the headers themselves. The winx project is just a collection for those headers, with the GE_::winx namespace containing Windows related classes and functions.

Where to start from

Let me be original: from the end.

int main()
{
    STRACE(trc, 0, ("w0 main\n"));

    stdx::New<Win>()->set_createhook(); /*forget this, by now*/ 

    return app::msgloop(CreateWindow(app::wndcls::frame(), "Hallo Window!",
        WS_VISIBLE|WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 600, 400,
        0,0, GetModuleHandle(0), 0)).run();

}

First thing: I used main instead of WinMain. But don't be fooled by this: the linker had been instructed to use the WINDOWS subsystem and to use mainCRTStartup as the entry point. We lose some useless Win16-only meaningful parameters, but we gain the C++ standard command line parsing (not needed here, but who knows!).

Forget about the stdx::New<Win>->set_createhook and you find the entire program in the return statement. (That is "we leave to die": although it seems pessimistic, if you are not in hurry ... you can do anything to do that, including live for one-hundred years or more!)

Now, app::msgloop is a constructor taking an HWND parameter. And run is the usual Windows application message loop (while GetMessage(.) { Translate and Dispatch }).

The HWND is obtained with the Win32 CreateWindow API (but anything creating a window works!), where the classname parameter is obtained with the app:wndcls::frame static function (that registers a WNDCLASSEX ) and GetModuleHandle(0) is used to retrieve the HINSTANCE for the executable module.

The run function will return when IsWindow for the passed HWND will be found false.

Well ... this is not perfectly orthodox in Windows (no PostQuitMessage call to make GetMessage to return 0) but works always, even if the main window is closed by an application unhandled syscommand, with no WM_QUIT message to be posted.

Window class registration

The key is to box the RegisterWinClassEx in a shortcutting class.

Class winx::wndcls is a box class containing an stdx::refcountable derived data structure to hold an ATOM and HINSTANCE values. wndcls constructors keep some convenient default parameters to fill-in a WNDCLASSEX structure, allocate a boxed structure referring it with a smart pointer, and call RegisterClassEx storing the return value.

The on_delete function of the internal structure calls UnregisterClass when no more references exist. Thus, app::wndclass(…) registers a Win32 window class on construction and unregisters it on the last destruction (copy and assignment pass the smart pointer, hence, sharing the ownership).

Since certain parameter value combinations are of frequent use, some static functions are available to shortcut such constructions: pane() registers a "genericpane" with IDI_APPLICATION icon and IDC_CURSOR and a COLOR_WINDOW background; gray() registers "genericgray" with COLOR_BTNFACE background, and frame() registers a frame with no background.

All of them take DefWindowProc as a window procedure. That makes all windows created with those classes to behave as default-behaving windows.

The message loop

Class app::msgloop is constructed on the stack starting from an HWND and an optional HACCEL.

Its loopbody function does the typical Get/Translate/Dispatch actions of a message loop body. It can be used as-is (for example, in calculation modal loops) or called by loop_while (that loops until a given bool reference becomes false, or until the related HWND exists) and run (that calls loop_while passing an always true variable).

Thus, the app::msgloop(...).run() will create and run the loop until the passed HWND (the newly created window) is destroyed.

Handling Windows messages

To stay with the Win32 API, Windows message handling happens through window procedures, associated to window classes, or to other "subprocedures" (like dialog procedure etc.) called by a system-defined default procedure. Handling messages for already existent windows can instead be done with subclassing (that is, replacing the window procedure with another).

This second method is more general, since it applies to every possible existing window. We can so create windows using the default procedure (DefWindowProc) and then subclass them as needed.

But there are two problems to solve:

  • A window procedure is a global-linkage function (a global or static member), while we would like to get messages into C++ class instances.
  • A modal window exists inside a single API call (think of DialogBox: a window exists while it runs, but doesn't before and after the call). When do you subclass it?

Thunking, delegates and whatsoever

Associating an HWND to an object instance and calling a member given the HWND value can be done with a technique named "thunking". It's the technique used in ATL / WTL. In fact, it is a hack: a data structure (something residing in the data segment) is executed after appropriate CPU registers loading as its bytes are the machine code to push a this pointer in the stack and do a jump.

This technique is generalized in the Don Clugston article on delegates.

Here I'll follow a different approach: HWND to this mapping is done … in a statically available std::map for a static wndproc that seizes the map and calls the instance on_message function.

The subclasser

Class winx::win_subclasser does exactly that, through the subclass and on_message function (and the subclass opposite reclass). And since it is a template taking a Derived class, we will have as many map and window procedures as C++ classes. And hence, each map will have as many entries as the total number of windows subclassed by the same C++ class. Hence, with an accurate definition of classes, seeking in the map can be not so time wasting.

As a consequence, we can sublclass the same HWND using different C++ classes and handle different HWND with different instances of a given C++ class. But not subclass twice an HWND with two instances of the same Derived. (And –in fact- it should be quite strange: why subclass twice to do the same things twice?)

The on_message function is what you'll override in your implementation classes. The base implementation (remember to call it!) handles the map HWND erasure at WM_NCDESTROY. The from_hwnd static function, seeks for the subclasser of a given type, creating it if not available.

Since win_subclasser is an stdx::refcountable and since the HWND map holds strong pointers, a subclass will exist until someone refers it, or until its referred HWND is destroyed.

Creation hook

The problem of handling the creation messages or handling modal windows' messages can be managed with the createhook class (that's also the base for win_subclasser): before creating the HWND (with any Windows API) just instantiate the appropriate createhook derived, and call set_createhook(). A Windows wndproc hook is installed, and the first WM_NCCREATE message seen will map the HWND with the C++ class instance. Then, the hook is deinstalled, and all subsequent messages received by the C++ class instance. The WM_NCDESTROY will cause the reclass thus eliminating the mapping.

Message routing

Receiving messages in a C++ class can be only a first step, in a complex application. When there is the need to "route" messages between objects (think of frames, views, documents and items) winmsgrouter / winmsglink classes can be a solution. They are respectively the nodes and the arcs of a generic graph. Nodes have virtual bool on_message and bool route_message functions, all taking a winmsg structure (a convenient wrapper for window procedure parameters and message routing information).

Routing functionality is –in fact- a specialization of the generics graph::link, router and routabledata templates, that, in a Windows related context become winx::winmsglink, winmsgrouter and winmsg, respectively.

Calling routemessage pushes in the parameter structure the arcs begin/end iterators, and calls the do_default function that iterates the arcs' destinations, calling on_message until someone will return true.

Of course, you can override winmsgrouter::on_message calling routemessage once again, propagating the message through the graph.

Arcs contain a property named metric, (accessible with get_ and set_), whose value is added as the message is routed. The idea is let on_message to have a way to know about how far a message comes from. By discarding too far-coming messages, you can have a very simple way to avoid routing loops.

Dynamic windows

An alternative way to distribute messages is the win class. It combines a winmsgrouter with a createhook. An internally defined source combines a win_subclasser with a winmsgrouter having on_message (that's called by the subclasser window procedure) calling routemessage. The createhook::set_hwnd is overridden to create a winmsglink between the win and the HWND associated source.

win also contains a self-pointing strong pointer that is initialized when the HWND is assigned to it, and cleared when WM_NCDESTROY is received or when the HWND is de-assigned. This makes the class to exist until some other strong pointer holds it or until its referred HWND exists.

Now, let's go back to the "/* forgot this by now */" of the previous code fragment: Win is a winx::win derived class defined in the test unit: instantiating one and calling set_createhook makes the window created soon after to be subclassed by a winx::win::source and its messages to be routed to Win. Since nothing else in the program will refer to Win, it will be deleted at HWND destruction (when the user will close the window).

To recap, the following picture shows the Windows and messages related classes.

winx classes

Handling Windows handlers

Windows handlers can be reference counted with value-semantic wrappers. In the stdx project, smart::hnd and smart::mappedhnd declare the strong and weak variants for those wrappers when the destruction policy is a simple call to a Win32 API.

This is certainly true for GDI object handles (like HBRUSH, HPEN etc.), where "handles.hpp" defines the HSxxx and HWxxx strong and weak variants with simple typedef-s.

Handling DCs

Device contexts are a different story:

  • They can be obtained in a variety of ways, and must be destroyed coherently.
  • There is certain "internal dependency" between DCs and GDI objects: when a GDI object is selected into a DC, a DC cannot be destroyed, and a DC cannot be destroyed while having GDI selected objects without causing memory leaks in the GDI.

To overcome these limitations, there is the need of several classes managing the correspondent acquisition and destruction policies. And there is also the need to keep track of the selections, in order to "deselect" before destroy.

Class stdx::smart (see the referring article), by itself, translates copies and assignments into calls to assign. A number of policies are defined in the winx::DCs namespace. The base for all is DCs::Save.

It declares HDC to be the alias_type, the value_type and the acquire_type, and act as a box for an inner strong pointer to a polymorphic data class, whose base is Save::data, that stores strong handles for the selected GDI object and the handle values for the "default DC selected object" (what was in the DC before any selection).

Assigning an HDC makes Save to allocate a new Save::data, while copy between Save makes the data to be shared.

The data deletion policy (Save::data::on_lastrelease) restores the selected data wit the default values (causing object deselection) and data deletion causes the GDI referred object to be released and –if not else referred- deleted.

This basic behaviour allows to save a DC state and restore it before leaving the DC unreferred. It doesn't delete the DC.

Policy derived from Save will:

  • override the acquire_type definition accordingly to their behaviour.
  • override the data structure definition by inheriting the Save's one and overriding on_last_release to do proper DC deletion.
  • override the assign functions to create to allocate their own data instead of the base one.

In general, different DC policies cannot assign each-other in a heterogeneous way, apart Save (since all policies derive from it). And since data are passed by smart pointers, the last releasing smart handle will use the policy that data carries on, that is the one belonging to the data type created during the first assignment.

Practically, no matter what smart handle causes a DC deletion: the deletion action is always coherent with the creation.

File "handles.hpp" typedefs the following DC smart handles:

  • DC_Save

    Just a typedef for stdx::smart<DCs::Save>: implements state saving and restoring, and objects retaining at selection.

  • DC_Client

    Accepts HWND in assignment, calling GetDC, and calls ReleaseDC on last release.

  • DC_Window

    Same as DC_client, but using GetWindowDC.

  • DC_Paint

    Accepts HWND, calls BeginPaint on acquisition and EndPaint on releasing.

  • DC_Mem

    Accepts an HSBITMAP, creates a screen-compatible memory DC and selects the bitmap in it.

  • DC_Mem_xy

    Accepts a SIZE, creates a screen-compatible memory DC, creates a compatible bitmap with those sizes, and selects it in the DC.

  • DC_MemPaint

    Accepts an HWND, and combines DC_Paint and DC_Mem creating an internal DC_Paint, a window-like sized bitmap and a DC_Mem that exposes. The alias DC (where all selection and drawing happens) is the memory DC. On last release, the bitmap is blit-ed in the Paint DC. Practically a double-buffer painter.

Other helpers

To help in certain annoying parameter passing and type definitions, some frequently used Win32 structs can be inherited by C++ classes to embed certain shortcuts or some arithmetic.

Class stdx::operators<Derived> defines all C++ arithmetic operators in terms of assignments and all relations in term of == and <.

Win32 POINT, SIZE and RECT arithmetic can be defined using winx::point, winx::size and winx::rect respectively. All of them inherit stdx::operators and the respective Win32 struct, and define assignment operators. They don't redefine Win32 functions: since they auto convert in their respective structs, they can be passed as parameters to those functions.

winx::String

The stdx::string template allows the management of shared sequence of data. When those data are char or wchar_t they're nothing more than ordinary strings.

When data are TCHAR, winx::strings::traits provides len and copy in terms of lstrlen and lstrcpy. At that point, winx::String is the typedef for stdx::string<TCHAR, winx::strings::traits>.

File "winstrings.hpp" contains the definitions –in winx namespace- for some function returning winx::String, instead of filling a preallocated LPTSTR.

Message filtering / dispatching

Class winx::msg_filter must be initialized with a winmsg (the parameter of on_message) and contains a set of is_something functions, that return true if the passed conditions are met.

Hallo world

It can be the following program:

/////////////////////////
//  w0.cpp

#include "stdafx.h"

#include "winx/winstrings.hpp"
#include "winx/coords.hpp"
#include "winx/handles.hpp"
#include "winx/win.hpp"
#include "winx/loop.hpp"

using namespace GE_;


class Win: public winx::win
{
    bool on_message(winx::winmsg& M)
    {
        winx::msg_filter m(M);
        return
            m.is_msg(WM_PAINT) && on_paint(M) ||
            winx::win::on_message(M);
    }

private:

    bool on_paint(winx::winmsg& M)
    {
        winx::DC_MemPaint dc(*this);
        dc.selectobject(CreateFont(-32,0,0,0,0,0,0,0,0,0,0,0,FF_SWISS,""));
        winx::rect rc ; GetClientRect(*this, rc);
        FillRect(dc, rc, GetSysColorBrush(COLOR_WINDOW));
        SetTextColor(dc, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(dc, GetSysColor(COLOR_WINDOW));
        DrawText(dc, winx::get_windowtext(*this), -1, rc, 
             DT_CENTER|DT_VCENTER|DT_SINGLELINE);
        
        return true; //don't default
    }

};

///////////////////////////////
//  WANING: since this is a windows program, to let the WINDOWS subsystem
//  to enter "main()", mainCRTStartup is declared as
//  module entry point for the linker.
//  
int main()
{
    STRACE(trc, 0, ("w0 main\n"));

    stdx::New<Win>()->set_createhook();

    return app::msgloop(CreateWindow(app::wndcls::frame(), "Hallo Window!",
        WS_VISIBLE|WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 600, 400,
        0,0, GetModuleHandle(0), 0)).run();

}

We already talked about main. Now is the time to look at its second code line.

The class Win is created on heap and its set_createhook member is called. As a consequence, a Windows hook is installed to spy all the windowproc calls. When CreateWindow is called, a new HWND is created and the WM_NCCREATE message is sent. This condition is seized by the hook that calls the Win::set_hwnd override, then dismisses itself. (Note: to let this operation to be thread-safe, only one thread is allowed to run during these steps by a Win32 critical-section internally managed.)

The win implementation of set_hwnd creates and maps a win::source, instantiates a winmsglink and use it to link the source to the Win instance. It will begin to receive the HWND messages.

The on_message implementation returns a complex Boolean expression whose side effect is … dispatching the message: to on_paint if it is WM_PAINT or to the base if it is anything else.

The on_paint function creates a DC_MemPaint for the HWND (*this converts into HWND automatically), then selects a newly created font.

Note that the HFONT returned by CreateFont is converted in HSFONT by the parameter passing, and stored in the DC policy.

The win::get_windowtext shortcut calls GetWindowText using a winx::String as a buffer. Hence no buffer allocation and handling is required.

At the return statement, the following effects occur:

  • dc is destroyed, and its data released.
  • The release policy restores the memory DC (thus deselecting the internal bitmap, and the passed font) and deletes it.
  • Then, it blits the bitmap that was drawn by the DC into an internally created PaintDC (that already called BeginPaint on its creation).
  • Then the data are deleted, causing the bitmap to be deleted and the PaintDC to be released.
  • The release of the PaintDC causes EndPaint to be called.

Conclusions

The usage of the reference counting pattern can greatly simplify Windows programming, allowing an implicit management of resources reducing the risk of leaks in the memory and in the system.

A convenient separation between data and related management (inside classes) and algorithm (namespaced functions) also allows a flexible and extendible way to simplify coding.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Italy Italy
Born and living in Milan (Italy), I'm an engineer in electronics actually working in the ICT department of an important oil/gas & energy company as responsible for planning and engineering of ICT infrastructures.
Interested in programming since the '70s, today I still define architectures for the ICT, deploying dedicated specific client application for engineering purposes, working with C++, MFC, STL, and recently also C# and D.

Comments and Discussions

 
Questionsomewhere it is all about 1s and 0s Pin
p3tr.lrx9-May-12 3:12
p3tr.lrx9-May-12 3:12 
GeneralMy vote of 5 Pin
p3tr.lrx9-May-12 3:06
p3tr.lrx9-May-12 3:06 
GeneralI don't like it Pin
xComaWhitex29-Nov-10 10:08
xComaWhitex29-Nov-10 10:08 
GeneralRe: I don't like it Pin
Emilio Garavaglia29-Nov-10 22:39
Emilio Garavaglia29-Nov-10 22:39 
GeneralMy vote of 1 Pin
Andy Bantly13-Nov-10 5:21
Andy Bantly13-Nov-10 5:21 
GeneralRe: My vote of 1 Pin
Emilio Garavaglia14-Nov-10 2:35
Emilio Garavaglia14-Nov-10 2:35 
QuestionCompiler Pin
GHop1-Mar-06 6:20
GHop1-Mar-06 6:20 
AnswerRe: Compiler Pin
Emilio Garavaglia2-Mar-06 22:31
Emilio Garavaglia2-Mar-06 22:31 
Questionver 2005 ? Pin
FBKK5-Oct-05 7:56
FBKK5-Oct-05 7:56 
AnswerRe: ver 2005 ? Pin
Emilio Garavaglia5-Oct-05 20:45
Emilio Garavaglia5-Oct-05 20:45 
GeneralRe: ver 2005 ? Pin
FBKK12-Oct-05 4:44
FBKK12-Oct-05 4:44 
Generalehmm question... Pin
HumanOsc19-Nov-04 8:18
HumanOsc19-Nov-04 8:18 
GeneralRe: ehmm question... Pin
Emilio Garavaglia20-Nov-04 2:58
Emilio Garavaglia20-Nov-04 2:58 
Generalhello... Pin
HumanOsc27-Sep-04 0:17
HumanOsc27-Sep-04 0:17 
GeneralPoll Pin
Emilio Garavaglia14-Sep-04 1:09
Emilio Garavaglia14-Sep-04 1:09 
GeneralRe: Poll Pin
Anonymous14-Sep-04 12:54
Anonymous14-Sep-04 12:54 
GeneralRe: Poll Pin
Emilio Garavaglia14-Sep-04 23:01
Emilio Garavaglia14-Sep-04 23:01 
GeneralRe: Poll Pin
Anonymous (the same as before)15-Sep-04 8:45
sussAnonymous (the same as before)15-Sep-04 8:45 
GeneralRe: Poll Pin
Emilio Garavaglia15-Sep-04 20:40
Emilio Garavaglia15-Sep-04 20:40 
GeneralRe: Poll Pin
Jonas Hammarberg25-Sep-04 21:55
professionalJonas Hammarberg25-Sep-04 21:55 
GeneralRe: Poll Pin
Emilio Garavaglia26-Sep-04 23:14
Emilio Garavaglia26-Sep-04 23:14 
GeneralRe: Poll Pin
Jonas Hammarberg26-Sep-04 23:28
professionalJonas Hammarberg26-Sep-04 23:28 
GeneralRe: Poll Pin
Don Clugston18-Nov-04 16:38
Don Clugston18-Nov-04 16:38 
GeneralRe: Poll Pin
Emilio Garavaglia18-Nov-04 23:13
Emilio Garavaglia18-Nov-04 23:13 
GeneralRe: Poll Pin
Don Clugston30-Nov-04 19:14
Don Clugston30-Nov-04 19:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.