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.
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:
type |
description |
types_wrp_hnd |
types_wrp_ptr |
SH |
What the wrapper stores to represent H |
H |
H* |
IH |
What the wrapper will receive as input parameter in functions |
const H& |
H* |
OH |
What the wrapper will return from its functions |
const H& |
H* |
RH |
What 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,
class W,
class t_traits,
class t_refCount
>
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() const
returns 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;}
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:
- declare a
struct
derived from WrpBase
specifying the required parameters, then
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
struct
s 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 Event
s 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 EGlobal
s and Event
s.
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:
- Wrappers for the same
HWND
chain together. The first attachment and the last release are used to subclass / reclass the wrapped HWND
.
OnFirstAttach
is overridden to do window subclassing.
- A subclassing window procedure "
Act
"s on the chained action relative to HWND
.
OnAction
is overridden to call a well-known virtual function with the same prototyping of the ATL PocessWindowMessage
.
Default
is overridden to call the previous window procedure if the action returns unhandled.
- 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);
static bool Default();
void Destroy(HWND& hWnd);
bool OnAction(const LPWndProcParams& a);
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); }
virtual bool ProcessWindowMessage(
HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam,
LRESULT& rResult, DWORD msgMapID) { return false; }
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);
bool CreateWnd(
HINSTANCE hInst,
LPCTSTR clsName,
LPCTSTR wndName,
DWORD dwStyle,
LPCRECT lprcPlace,
HWND hwndParent,
HMENU hMenu,
LPVOID lpParams
);
};
Notes:
IMessageMap
is an interface declaring the ProcessWindowMessage
function in ATL style.
- derives from
ERefChaining
, but it uses XRefChainWnd
. It derives from XRefChain
, but also hosts a WNDPROC
function pointer and a recursion counter.
- derives from
EChainedAction
, using LPWndProcParams
. XWndProcParams
is a struct
taking hWnd
, uMsg
, wParam
, lParam
and lResult
.
- derives from
EAutoDeleteFlag
. If set to true
(the default is false
) the wrapper deletes itself when the window is destroyed
- 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);
LPCTSTR wcname = _T("W1MainWnd");
NWin::SWndClassExReg wc(true, hInstance, wcname);
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::map
s, list
s 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:
#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);
LPCTSTR wcname = _T("W1MainWnd");
NWin::SWndClassExReg wc(true, hInstance, wcname);
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)