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

Writing Win32 Apps with C++ only classes

Rate me:
Please Sign up or sign in to vote.
4.97/5 (32 votes)
1 Mar 2004CPOL21 min read 133.9K   3.5K   109   23
C++ classes and wrappers to write W32 apps without MFC, ATL or other (part 1?)

Introduction

I wrote this article a few for joke and few as a challenge to myself: I'd been a Win32 C programmer, but, with C++, I always relied on MFC, WTL, .NET, WxWin or some "framework" or wrapper.

One of the advantages is that they "simplify" certain operations by hiding certain details. In contrast, when you have to do something different than usual, the entanglement that exist between the framework classes can make the work quite hard.

Do you ever have the feeling - when overriding certain MFC classes or function - to hack-in somebody else's defined behavior? How often did you find with strange "side effects" due to the fact that Document, Frames and Views often destroy each other in a not always obvious order?

What follows is a way to introduce a different approach. Of course, I don't have the man power of the entire MS AFX team, so don't expect a full replacement of WTL or ATL. I just want to introduce a different possible point of view.

And because I'd like to make the proposed classes reusable, I tried a design able to reduce the classes entanglement to the minimum. In particular, if class A needs class B, I wanted to avoid that class B also needed class A. Thus, you can use this classes also outside my framework, may be even in a MFC application as well.

However, I only relied to STL containers and algorithms and to some CP authors' ideas: in particular Paul Bartlett and Alex Farber. (I didn't reuse their code, but - in fact, I reinterpreted their ideas.)

Conventions

I deployed all the classes in agreement with this article. I also used namespaces, an put all the namespaces in a root namespace named "GE_". If this names gives trouble to some other libraries, just replace all the occurrence in files with something else.

Shared Ownership Wrappers

This is the key for all this implementation: Win32 API is full of handles of various kinds, you must "obtain", "use" and "release".

In my intention, a wrapper should not rewrite C API: I don't feel any particular need to replace ShowWindow(hWnd, SW_SHOW) with pWnd->ShowWindow(SW_SHOW): the "actors" are exactly the same, and rewriting thousands of prototypes just to put the first parameter outside the parenthesis ... it's not a good investment, for me.

More interesting is to wrap handles to give them protection from improper destruction politics, or forgetting to destroy them, or offering more effective constructors for functions having often many parameters that takes often same values.

In this implementation, a Wrapper is something similar to a "reference counting smart pointer" (in fact, it is a generalization): it may be an "owner" of the handle it wraps (thus, destroying it when no more "owners" are holding it) or an "observer" that behaves as "invalid" when all "owners" have left.

But, because we may have a number of different handles with different management politics, I decided to split the implementation in a number of template classes inheriting from each other.

The Architecture

The heart of all the mechanism is a cooperation between a WrpBase and a XRefCount.

WrpBase provides the "protocol" for a wrapper: refers to a "reference counter" and can be "Attached" and "Detached" to the object it wraps. It also can be set as "owner" or "observer", be "Valid" or not, and return STL iterators to walk onto a wrapper collection.

XRefCount, in turn, maintains information about how many (and eventually "who") are the wrappers wrapping a given value or handle, and provides some helper functions to the WrpBase to retrieve and update those information.

WrpBase is designed to be derived (one of its template parameters, W, is the class you derive from it) and calls all it functions after static_cast-ing its "this" pointer to W*. This allows you to override its functions.

Also, the behavior of WrpBase depends on a number of "helpers" that are provided by a traits class WrpBase inherits from. The purpose of this "traits class" is also to provide a convenient number of typedef-s that defines the way the wrapped object is stored in the wrapper, is received as a parameter from member functions, is given as a return value, or is de-referenced.

Traits classes are implemented in various versions and inherits the type definition from "types classes".

To close everything into something that can be assigned, copied etc., a Wrp class is then designed to inherit from a WrpBase or derived class, applying assignment operator, copy constructors, equality (==) and ordering (<) operators, access (->) and dereference (*) and so on, using the WrpBase defined protocol (after your customization, if any).

The following picture gives a sketch on all this stuff.

Wrp architecture

Inheritance goes from top to bottom. XRefCount knows (because of a template parameter) about "YourClass", Wrp knows because of inheritance, and calls WrpBase prototyped functions in the way you can eventually override them. WrpBase, in turn, uses what inherited from "traits" and from "types".

The implementation

Types

"types" is implemented in two different ways:

  • types_wrp_hnd<H>: suppose H is a handle or a "small object" or a pointer or ... something that can be casted to LPVOID without information loss and that is stored "as is".
  • types_wrp_ptr<H>: suppose H is an object on the heap, we have to refer to it with a smart pointer. Thus an H* is stored by the wrapper and the wrapper will have a "pointer semantics".

Both the classes define the following types:

typedescriptiontypes_wrp_hndtypes_wrp_ptr
SHWhat the wrapper stores to represent HHH*
IHWhat the wrapper will receive as input parameter in functionsconst H&H*
OHWhat the wrapper will return from its functionsconst H&H*
RHWhat the wrapper will return as "dereference"const H&H&

In specific implementations, may be SH, IH, OH and RH can be different (for example: you can return a proxy object to store a reference ...).

In any case, it is required that implicit conversion can work when converting IH into SH, SH into OH, and OH into IH.

Traits

"traits" is implemented in three different ways:

  • traits_wrp_hnd<H, Types>: suppose H is a Handle. Most of its functions need an override once H is known and its management policy is defined.
  • traits_wrp_ptr<H,Types>: suppose H is a "pointed object" (the wrapper operates as H*) and implements Create as new and Destroy as delete. Conversions between pointers are operated with static_cast.
  • traits_wrp_dynptr<H,Types>: like before, but using dynamic_cast.

Normally, the "Types" parameter defaults to types_wrp_hnd<H> in the first case and to types_wrp_ptr<H> in the other two cases. But - for some particular cases - other solutions can be afforded.

The purpose of the "traits" classes is to provide some low-level helper functions to standardize to the upper "layer" classes an interface to operate on the various "types".

An example is the code of traits_wrp_hnd itself:

template<class H, class Types=types_wrp_hnd<H> >
struct traits_wrp_hnd: public Types
{
    typedef Types TT;
    static bool equal(TT::IH left, TT::IH right) { return left == right; }
    static bool less(TT::IH left, TT::IH right) { return left < right; }
    static LPVOID Key(TT::IH h)  { return (LPVOID)h; }
    template<class A> static void from(const A& a, TT::SH& h) { h = (H)a; }
    static TT::IH invalid() {static TT::SH h(NULL); return h; }
    static TT::OH Access(TT::IH h) { return h; }
    static TT::RH Dereference(TT::IH h) { return h; }
    void Create(TT::SH& h) { h = invalid(); }
    void Destroy(TT::SH& h) { h = invalid(); }
};

Note that "invalid" is implemented to return a "NULL" value. For certain kind of handles, this will be probably overridden.

WrpBase

It is defined as:

template<
    class H,    //what we are wrapping
    class W,    //outside wrapper (derived from here)
    class t_traits, //invalid H behaviour
    class t_refCount    //reference counter type used
>
class WrpBase:
    public t_traits
{
public:
    typedef WrpBase TWrpBase;
    typedef W TW;
    typedef t_refCount TRefCount;
    typedef t_traits TTraits;
    typedef H TH;
    typedef TTraits::TT TT;
    friend WrpBase;
    friend TRefCount;
    friend TTraits;
protected:
    TT::SH _hnd;
    t_refCount* _pRC;
    bool _bOwn;
    ...
}

It provides storage for a SH member, a pointer to a t_refCount class (supposed to have the same prototyping as XRefCount_base), and a bool defining if this wrapper is an "owner" or an "observer". It also implements the following:

  • void Attach(TT::IH h) attaches the wrapper to a passed "dumb value". Existing reference counters for the value are searched and, if not found, created.
  • void AttachWrp(const WrpBase& w) attaches the wrapper to the value carried by another wrapper.
  • template<class A> void AttachOther(const A& a) attaches the wrapper to the value that can be detected by conversions from the A type.
  • void Detach() detaches the wrapper. Calls Destroy (inherited from TTraits) if we are the last owner wrapper associated to the wrapped value.
  • bool IsValid() const returns if the wrapper must be considered valid and so the wrapped object.
  • bool IsOwner() constreturns if the wrapper is an "owner" wrapper or not.
  • void SetOwnership(bool bOwn) makes the wrapper an "owner" (true) or an "observer" (false).
  • typedef TRefCount::iterator iterator; static void get_range(iterator& first, iterator& last, TT::IH h) gets the iterator range from an eventual wrapper collection associated to the wrapped value. If first==last, the collection is empty or not existent.

They are implemented in terms of other functions (always through the pThis() function, that converts this into W*) and in terms of TRefCount functions. It also provides to the TRefCOunt a set of "do nothing" call back functions:

  • void OnFirstAttach(XRefCount_base* pRC) called when the reference count is counting the very first reference for the wrapped object.
  • void OnAddRef(XRefCount_base* pRC) called every time a new reference is added.
  • void OnRelease(XRefCount_base* pRC) called every time a reference is released.
  • void OnLastRelease(XRefCount_base* pRC) called when the last release happens.

Reference Counting

Must derive from XRefCount_base, overriding its functions:

class XRefCount_base
{
public:
    bool _bValid;
    XRefCount_base() { _bValid = true; }

    int owners() { return 0; }
    int observers() { return 0; }
    int reference() { return 0; }

    void AddRef(W* pW, bool bOwn)
    {}
    
    bool Release(W* pW, bool bOwn)
    {return false;}

    //doesn't iterate
    iterator begin() { return NULL; }
    iterator end() { return NULL; }
};

It's actually implemented in two versions:

  • XRefCount<W>: W is supposed to be WrpBase derived. Implements owners and observers reference counting. (reference() is the sum of the two). It auto-destroys on Release() when no more reference remains, returning true (else, false). Doesn't provide iterators (return "first" and "last" to random equal values).
  • XRefChain<W>: keeps a list of W* pushing in front on AddRef and removing on Release. Auto-deletes when the list becomes empty.

The first can be used with types that requires only a reference counting, the last with types you need to keep track of "who" are the wrappers.

Reverse Mapping

XMap hosts a static std::map<LPVOID, XRefCount_base*>, providing Map, Unmap and Find functions.

Wrappers know what they wrap, and know who is the associated reference counter. But if we're assigning a value to a wrapper, we must have a way to find out (in the case that "value" is already wrapped by someone else) if there is (and who is) the associated XRefCount.

One of the function that comes with the traits classes is Key(IH). It converts the wrapped type into an LPVOID.

This can be done with C-syle cast (as with traits_wrp_hnd), static_cast (as with non polymorphic pointers, in traits_wrp_ptr) and dynamic_cast (for polymorphic pointers, as in traits_wrp_dynptr).

Particularly interesting is the last one: given a complex object (with many bases) different pointers to different components can be different (in terms of absolute value) but a dynamic_cast to LPVOID always return the same base address (the one of the entire object). This allows to share the ownership of a complex object between different smart pointers referring to the different components: they all can find out the same reference counting object.

Operators ( Wrp<B> )

Conversion constructors, copy constructors, assignment operators etc. cannot be inherited as ordinary functions. Their override requires particular attention, so I preferred to concentrate all this stuff into a specific template class. Wrp<B> is the "upper layer class". B is the class Wrp is made to derive from. It can be every WrpBase derived class. It implements assignment, copy, conversion and destruction in terms of Detach/Attach.

Some specific cases: smart pointers

To specialize a wrapper, a method could be the following:

  1. declare a struct derived from WrpBase specifying the required parameters, then
  2. typedef a Wrp<yourstruct>, to give assign and copy functionality.

A very simple case when this happens is the definition of smart pointers:

template<class T>
struct Ptr
{
    struct StatBase:
        public WrpBase<T, StatBase, 
          traits_wrp_ptr<T>, XRefCount<StatBase> >
    {};

    struct DynBase:
        public WrpBase<T, DynBase, 
          traits_wrp_dynptr<T>, XRefCount<DynBase> >
    {};

    typedef Wrp<StatBase > Static;
    typedef Wrp<DynBase > Dynamic;
};

The two structures define the behavior for a static_cast and a dynamic_cast pointer wrapper.

If A is a class, a smart pointer to A can be ...

typedef Ptr<A>::Dynamic PA;.

Smart Handle

In the same way, we get "smart handle" using:

template<class H, class W>
struct HndBase:
    public WrpBase<H, W, traits_wrp_hnd<H>, XRefCount<W> >
{};

Note that, while Ptrxxx structs close the wrapper inheritance (they give themselves to WrpBase as W parameter) HndBase doesn't: there is still a W parameter.

This is because, while for pointer it is clear that new and delete are the creation and destruction policy (Create and Destroy comes from the traits classes this way), for handles, it's all still to be defined. Different handles have different creation and destruction API. HDC, then, have many of them! So we must complete later.

Of course, for certain particular objects, you may also want to do something else than "new" or "delete": in this case, you must do yourself the definition of a struct that derives from WrpBase (and closes it inheritance) and then wrap it into a Wrp.

Chained actions

Using XRefChain as WrpBase parameter, we can chain "wrappers to the same entity" together and walk on them.

An interesting thing that can be done is to associate actions to this "walking". The idea is to provide - for example- a way to dispatch Windows messages to HWND wrappers. But not only.

EChainedAction<W, I, A> is a class designed do be derived (W is its derived class) that associates an action having A as parameter to the walk on the iterator I, that must be an STL compliant forward iterator, that dereferences into W* (whose I::operator*() returns a W*). Such an iterator can be obtained, for example, by WrpBase::get_range(...).

The function Act(const A& a, iterator first, iterator last), iterates from first to last calling the member function OnAction(const A& a) that is supposedly overridden in W.

The way the iteration is done, however, is not a pure flat loop: it is instead a recursion over stored parameters: Act fills in a data structure on stack and saves its address into a per-thread saved static variable, keeping the previous address (a per-thread variable is a variable that's accessed through a map whose key is the current thread ID) than call Default(). Default retrieves the data structure and iterates calling OnAction. If OnAction returns true, Default returns immediately true, otherwise iterates to the next element until "last" is reached. At that point returns false.

Thus, in your OnAction override, you can:

  • Simply return false. The iteration will continue to the next chained element.
  • Simply return true. The iteration is halted.
  • Call OnAction where appropriate. The iteration to the next elements is anticipated and the control returned to you.

More or less, it's like subclassing a window procedure with another, and if needed, call the previous. An arbitrary number of times.

Events

A combination between smart pointers and chained actions are events.

Here, they are implemented as "functors" to be declared as member variables of a class (the event source) that are attached by internal objects (the "sinks") that dispatch an action to registered "receivers".

Event<A> is a struct that's few more than an operator()(const A&) member, that supposes that the event has to be wrapped by chainable wrappers. The operator gets the chain, and calls Act.

EventRcv<D> is a base for a generic derived class (D), providing RegisterEvent<A>(Event<A>&, bool(D::*pmemfun)(consty A&) ) and UnregisterEvent<A>(Event<A>&). The first function creates an XTypedSync<A,R> that associates the event to the given member function. The second clears the association.

XTypedSync<A,R> derives from XSync<A>, that is a chainable wrapper for Events having parameter A. XSync implements OnAction delegating to a virtual DoAction function, that is implemented in the derived XTypedSync, calling the given member function (via a pointer to a member saved during association). The use of virtual function is required because different XTypedSync (referring to different receivers, so having different R parameters) must chain together all as XSync<A>.

Thus, calling an event, will call the registered member function for all the associated receivers.

Registering a WNDCLASS

If you think that filling in the field of certain data structure like WNDCLASSEX is a tremendously annoying task, this wrapper is for you:

SWndClassExReg registers a window class, un-registering it when no more references are available.

It derives from WrpBase, customizing Destroy.

Constructors attempt to fill a WNDCLASSEX with the supplied parameters:

  • A version loads from resources with the passed ID (icon and cursor), the other uses the passed raw values.
  • Both the constructors register the window class and save the returned ATOM.
  • The wrapper Destroy function (it is called when the last owner wrapper is detached) calls UnregisterWndClass, passing the saved ATOM.

The message loop (SMsgLoop)

The message loop is implemented by SMsgLoop. You can create as many instances you need. Typically one will be in WinMain, while others may be where "modal loops" are required (for example: when asking mouse coordinates to detect where to place an object).

The "loop" itself is implemented by a LoopBody function (that is not itself a loop) implementing message retrieving and dispatching, with also a bit of message filtering / translation and idle processing. Such a function is called by LoopWhile that loops until a passed bool reference becomes false or until WM_QUIT is received. LoopWhile is called by Loop, that provides an "always true" value to LoopWhile.

This "breaking the loop" in three components allow to use SMessageLoop also for modal loop to be run inside an application (using LoopWhile) or to dispatch message during a loop calculation (calling LoopBody from inside the hosting loop).

SMessageLoop provides also a member to store an HWND (that can be the application main window) whose destruction will cause WM_QUIT to be posted.

If SMsgLoop is created with the bAutoQuit parameter set to true, the first window that is created through that message loop becomes the message loop main window.

Idle processing and Message filtering

Idle processing and message filtering are implemented through global events.

This solution avoids the risk to create entanglement between the message loop implementation and - for example - a window implementation. You can use my windows also in other frameworks where my message loop is not used. The only requirement is to properly generate the events.

SIdleEvent and SMsgFilterEvent are both EGlobals and Events.

The SIdleEvent parameter is the SMsgLoop that calls the event. If you are originating yourself the event, you can pass NULL.

SMsgFilterEvent parameter is the Win32 MSG structure. In your event hander, return true to indicate that the passed message was filtered.

Wrapping HWND: subclassing windows

HWND wrappers must have the capability to receive window messages. This implies them to be both chaining WrpBase and EChainedAction, but that's not enough.

There's also the need to get the messages from the system. This requires a mechanism of subclassing/reclassing. And also, there is the need to dispatch messages to possibly different member functions. There are different approaches to do this:

  • MFC-like: member functions are mapped as addresses with the correspondent message IDs. When a subclass window procedure receives a message, it scans the map searching a match and then calls the corresponding function. A set of macros can be provided to assist a static-map filling.
  • ATL-like: a set of macros provides the fragments of code necessary to identify messages and dispatch them to the indicated functions. The macros also override a well-known virtual function that is called by a subclassing window procedure.
  • .NET-like: a subclassing window procedure cracks the messages, dispatching them to a set of well-known members, each of which raise a different event.

None of those approaches are perfect: the first two strongly deal on macros, and the third requires a heavy data structure and is not "symmetrical": there is an object that "owns" the HWND and others attaching to it.

This "imperfections" have been compensated by a number of wizards that help in compiling message maps or chaining event handlers. Considering the big number of messages we have to deal with, they are probably the best compromise between programming techniques and tool usability. In my experience, I found the ATL/WTL approach more flexible, so I did the following:

  1. Wrappers for the same HWND chain together. The first attachment and the last release are used to subclass / reclass the wrapped HWND.
  2. OnFirstAttach is overridden to do window subclassing.
  3. A subclassing window procedure "Act"s on the chained action relative to HWND.
  4. OnAction is overridden to call a well-known virtual function with the same prototyping of the ATL PocessWindowMessage.
  5. Default is overridden to call the previous window procedure if the action returns unhandled.
  6. To store the previous window procedure, we need a place that is shared among all the wrappers of the same window. Thus the XRefChain must be derived to host this value.

The result for all this is EWnd.

Its declaration is the following:

class EWnd:
    public IMessageMap,
    public NWrp::WrpBase<HWND, EWnd,NULL,traits_wrp_types<HWND>, XRefChainWnd>,
    public NWrp::EChainedAction<EWnd, EWnd::iterator, LPWndProcParams>,
    public NUtil::EAutodeleteFlag
{
public:
    void OnFirstAttach(NSmart::XRefCount_base* pRC); //do window subclassing
    static bool Default();  //do superwndproc calling
    void Destroy(HWND& hWnd);  //destroy HWND (only owners does !)
    bool OnAction(const LPWndProcParams& a); //call ProcessWindowMessage
    
    //copy and assign
    EWnd() {SetOwnership(false);}
    EWnd(const EWnd& w) { SetOwnership(false); AttachRefCounting(w); }
    EWnd& operator=(const EWnd& w) { AttachRefCounting(w); return *this; }
    EWnd& operator=(HWND h) { Attach(h); return *this; }
    explicit EWnd(HWND h) { SetOwnership(false);  Attach(h); }

    //IMessageMap defaul implementation
    virtual bool ProcessWindowMessage(
        HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam, 
        LRESULT& rResult, DWORD msgMapID) { return false; }

    //some helpers common for all the windows
    LRESULT ForwardNotifications(UINT uMsg, 
        WPARAM wParam, LPARAM lParam, BOOL& bHandled);
    LRESULT ReflectNotifications(UINT uMsg, 
        WPARAM wParam, LPARAM lParam, BOOL& bHandled);
    BOOL DefaultReflectionHandler(HWND hWnd, UINT uMsg, 
        WPARAM wParam, LPARAM lParam, LRESULT& lResult);

    //window creation
    bool CreateWnd(
        HINSTANCE hInst,
        LPCTSTR clsName,
        LPCTSTR wndName,
        DWORD dwStyle,
        LPCRECT lprcPlace,
        HWND hwndParent,
        HMENU hMenu,
        LPVOID lpParams
        );
};

Notes:

  1. IMessageMap is an interface declaring the ProcessWindowMessage function in ATL style.
  2. derives from ERefChaining, but it uses XRefChainWnd. It derives from XRefChain, but also hosts a WNDPROC function pointer and a recursion counter.
  3. derives from EChainedAction, using LPWndProcParams. XWndProcParams is a struct taking hWnd, uMsg, wParam, lParam and lResult.
  4. derives from EAutoDeleteFlag. If set to true (the default is false) the wrapper deletes itself when the window is destroyed
  5. the wrapper is always created as "non owner" (observer). Setting it as "owner" will destroy the HWND when all the owners are detached.

The "idiot" program (adjective, not genitive!)

Too simple to be useful, apart demonstrates all the stuff at work.

#include "stdafx.h"

#include "NLib/MsgLoop.h"
#include "NLib/Wnd.h"

using namespace GE_;

int APIENTRY _tWinMain(HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    NWin::SMsgLoop loop(true);  //instantiate a loop
    
    LPCTSTR wcname = _T("W1MainWnd");
    NWin::SWndClassExReg wc(true, hInstance, wcname); 
    //instantiate a window class
    
    NWin::EWnd wnd; 
    wnd.CreateWnd(hInstance, wcname, NULL, 
        WS_VISIBLE|WS_OVERLAPPEDWINDOW, 
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT), 
        NWin::SSize(600,400)), NULL, NULL, NULL);

    loop.Loop();

    return 0;
}

Create a window based on DefWndProc (passed as default parameter to SWndClassExReg) that does ... really absolutely nothing ... in 76KB executable.

Not the shortest exe I've ever seen in my old life, but consider all traits functions, std::maps, lists etc. They improve flexibility but have a cost!

Hello World: GDI wrappers

AS a first step, we need to derive EWnd into SWnd and implement a message map dispatching WM_PANT.

Then we need message map macros,...

Then we need "featured" GDI object wrappers ... or include the GDI+ library.

Message map macros

Inspired by ATL macros, they've the same names to allow the IDE wizard to do their dirty work in the same way, but .. they're not the same of ATL (they'd been designed to work into an EWnd wrapper, not CWnd or CWindow!). They are included in MessageMap.h. ATL basic macros work also well, but not WTL macros.

Message cracker macros, again have same names and parameters of WTL ones, but different implementation.

Note: I obtained them with a heavy use of "find and replace" from the original sources. They are hundredths, so I cannot test all them singularly, so, because the process had been the same for all, and because the ones I tested worked, ... I suppose all of them work.

But it is very easy to test as they are used. Simply ... don't forget!

At this point, a very simple "hello world" program could be this:

// W2.cpp: W32 test application

#include "stdafx.h"

#include "NLib/MsgLoop.h"
#include "NLib/Wnd.h"

using namespace GE_;

class CMainWnd: public NWin::EWnd
{
public:
    BEGIN_MSG_MAP(CMainWnd)
        GETMSG_WM_PAINT(OnPaint)
    END_MSG_MAP()
    void OnPaint();
};


int APIENTRY _tWinMain(HINSTANCE hInstance, 
  HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    NWin::SMsgLoop loop(true);  //instantiate a loop
    
    LPCTSTR wcname = _T("W1MainWnd");
    NWin::SWndClassExReg wc(true, hInstance, wcname); 
    //instantiate a window class
    
    CMainWnd wnd; 
    wnd.CreateWnd(hInstance, wcname, NULL, 
        WS_VISIBLE|WS_OVERLAPPEDWINDOW,
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT), 
        NWin::SSize(600,400)), NULL, NULL, NULL);

    loop.Loop();

    return 0;
}

void CMainWnd::OnPaint()
{
    PAINTSTRUCT ps;
    BeginPaint(*this, &ps);
    
    NWin::SRect R; 
    GetClientRect(*this, &R);
    DrawText(ps.hdc, _T("Hallo World !"),-1, 
          R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);

    EndPaint(Value(), &ps);
}

Isn't it nice? Not yet: what are those BeginPaint - EndPaint pairs? Why don't we wrap HDC into a wrapper that manages properly the Begin-End or Get-Release pairs? And what about saving and restoring the state before quit ?

Wrapping HDC

The idea to save the state of a DC when wrapping, it seems good, but ... where should be the state stored ?

There are two possible answers:

  • In the wrapper itself: every wrapper of the same DC keeps its own state, saving it on Attach and restoring it on Detach. This is useful in "short life wrapper": you attach one at the beginning of a function and you are sure that its destruction (at the end) will restore the DC and close it. Nested function calls create a series of wrappers that will be destroyed in reverse order. So it's OK.
  • In the reference counter object: the saved state is shared among all the wrappers of the same HDC, and is restored on the last owner detachment. This can be good in "long life wrappers" that can be passed as values between functions or that keep the wrapped object alive until other objects refer to it. This is the case for the "SuperWndProc" in EWnd, but it is not the case for an HDC: its normal life doesn't survive the process of a command.

Given the above consideration, I provided a set of HDC wrappers that can interoperate (they use the same XRefCount) but with different Attach/Detach policy.

All classes are based on SHdcSave_base<W>, that override OnAddRef and OnRelease to save and restore the DC state. Then:

  • SHdcSave(HDC): it's nothing more than the closure of SHdcSave_base, with a constructor that takes an HDC as an argument.
  • SHdcPaint(HWND): Overrides Create calling BeginPaint and Destroy calling EndPaint. The constructor keeps an HWND. The PAINSTRUCT is accessible as read_only.
  • SHdcClient(HWND): Create calls GetDC and Destroy calls ReleaseDC.
  • SHdcWindow(HWND): Create calls GetWindowDC and Destroy calls ReleaseDC.
  • SHdcMem(SIZE): creates a memory DC compatible with the screen, and keeps a compatible bitmap with the given SIZE selected into it. Good for double buffering.

With SHdcPaint, the OnPaint method becomes this:

void CMainWnd::OnPaint()
{
    NGDI::SHdcPaint dc(*this);

    NWin::SRect R;
    GetClientRect(*this, R);
    DrawText(dc, _T("Hallo World !"),-1, 
         R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}

HPEN, HBRUSH, HFONT

GDI objects can be created in a variety of ways, but always destroyed with the same API: DeleteObject.

Thus, SGdiObj<H> wraps any HGDIOBJECT with an HndBase, overriding the Destroy callback by calling DeleteObject.

It is important that GDI object destruction takes place when objects are not selected into some HDC. This can be obtained by initializing the GDI wrappers before to initialize HDC wrapper. This will let HDC wrapper to go out of scope before restoring its state and leave free the GDI object that can be destroyed by their own wrappers.

Here is the "OnPaint" method, with a different font:

void CMainWnd::OnPaint()
{
    NWin::SRect R; 
    GetClientRect(*this, R);

    NGDI::TFont font(CreateFont(60,0,0,0, FW_BOLD,
        0,0,0, 0,0,0,ANTIALIASED_QUALITY, FF_SWISS, NULL));
    
    NGDI::SHdcPaint dc(*this);
    SetTextColor(dc, GetSysColor(COLOR_WINDOWTEXT));
    SetBkColor(dc, GetSysColor(COLOR_WINDOW));
    SelectObject(dc, font);

    DrawText(dc, _T("Hallo World !"),-1, 
        R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}

Note that font is created before dc, hence dc will be destroyed first. When this happens, "RestoreDC, and then, EndPaint" are called. RestoreDC will deselect the font, EndPaint will close the painting. At that point, font destruction will delete the GDI object.

Further issues

OK: now the big picture is done. I'm going to further refinements and to some more specific and detailed classes. But is is probably a better subject for subsequent articles.

History

  • 2004 - Feb - 05: First publishing.
  • 2004 - Feb - 17: Some code updates: using typename keyword in template classes.
  • 2004 - Mar - 02: Some "coords.h" bugs fixed (SRect arithmetic)

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

 
GeneralMy vote of 4 Pin
arbinda_dhaka13-Jan-13 8:59
arbinda_dhaka13-Jan-13 8:59 
GeneralMy vote of 5 Pin
Michael Haephrati24-Jan-12 10:32
professionalMichael Haephrati24-Jan-12 10:32 
QuestionCan't compile in vs2005 Pin
wk198303183-Jun-08 22:11
wk198303183-Jun-08 22:11 
AnswerRe: Can't compile in vs2005 Pin
Emilio Garavaglia4-Jun-08 21:21
Emilio Garavaglia4-Jun-08 21:21 
GeneralPart 2 is out Pin
Emilio Garavaglia25-Mar-04 21:23
Emilio Garavaglia25-Mar-04 21:23 
Part 2 is out at http://www.codeproject.com/cpp/GE_NLIB_2.asp[^]
From now I will no more maintain the code in this article.
For updated code, please refer to part 2.

Thanks to all for appreciation !;)
GeneralCode updated Pin
Emilio Garavaglia2-Mar-04 6:44
Emilio Garavaglia2-Mar-04 6:44 
GeneralCode updated Pin
Emilio Garavaglia17-Feb-04 5:57
Emilio Garavaglia17-Feb-04 5:57 
GeneralGood, but... Pin
Rui Dias Lopes5-Feb-04 7:09
Rui Dias Lopes5-Feb-04 7:09 
GeneralRe: Good, but... Pin
Emilio Garavaglia5-Feb-04 20:59
Emilio Garavaglia5-Feb-04 20:59 
QuestionWTL? Pin
zarzor5-Feb-04 6:29
zarzor5-Feb-04 6:29 
AnswerRe: WTL? Pin
Emilio Garavaglia5-Feb-04 21:01
Emilio Garavaglia5-Feb-04 21:01 
AnswerRe: WTL? Pin
John M. Drescher6-Feb-04 4:30
John M. Drescher6-Feb-04 4:30 
GeneralRe: WTL? Pin
Rob Caldecott8-Feb-04 22:22
Rob Caldecott8-Feb-04 22:22 
GeneralRe: WTL? Pin
Emilio Garavaglia9-Feb-04 2:46
Emilio Garavaglia9-Feb-04 2:46 
GeneralRe: WTL? Pin
John M. Drescher9-Feb-04 7:30
John M. Drescher9-Feb-04 7:30 
GeneralRe: WTL? Pin
Rob Caldecott9-Feb-04 7:51
Rob Caldecott9-Feb-04 7:51 
GeneralRe: WTL? Pin
melwyn10-Feb-04 18:47
melwyn10-Feb-04 18:47 
AnswerRe: Lots wrong with WTL! Pin
TomM8-Mar-04 13:28
TomM8-Mar-04 13:28 
GeneralRe: Lots wrong with WTL! Pin
Emilio Garavaglia11-Mar-04 3:33
Emilio Garavaglia11-Mar-04 3:33 
GeneralRe: Lots wrong with WTL! Pin
John Carson28-May-04 20:31
John Carson28-May-04 20:31 
GeneralYou might be interested in the VCF [modified] Pin
Jim Crafton5-Feb-04 6:21
Jim Crafton5-Feb-04 6:21 
GeneralRe: You might be interested in the VCF Pin
Emilio Garavaglia5-Feb-04 21:03
Emilio Garavaglia5-Feb-04 21:03 
GeneralRe: You might be interested in the VCF Pin
Jim Crafton6-Feb-04 7:52
Jim Crafton6-Feb-04 7:52 

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.