I'm writing an object-oriented window API wrapper for Windows in D, and I'm having a (non-language-specific) design problem.
Windows requires that all windows be previously registered with RegisterClass
; extending an existing class requires replacing the window procedure. Furthermore, there seem to be two kinds of window handles: HWND
s that need to be disposed (via DestroyWindow
), and HWND
s that don't (e.g. from other applications).
I have created a Window
class, and so long as I'm just wrapping the ShowWindow
, UpdateWindow
, FindWindow
, and other such methods, everything is fine and dandy. But as soon as I try to add a constructor with a className
parameter to my class that calls CreateWindow
, I run across a problem:
- The given
className
must already have been registered byRegisterClass
. - In order to register a new window class, I would need to make my subclasses of
Window
to somehow callRegisterClass
before trying to create a new Window, either directly or indirectly. - In order for my design (and inheritance) to make sense, i would need to make sure that, for any given subclass of
Window
, all instances are actually instances of the same window class; namely, that allclassName
s from a particular subclass are identical. (This is because the window procedures for all instances of a particular window class need to be the same.)
The problem is, there's no way to have an abstract static
method (in order for Window to be able to ask the subclasses for their class info, and to register them once), and so I am forced to say something like CreateWindow(this.className, ...)
in order to create a new window, which easily becomes problematic if my subclasses don't respect this rule, and give me a different class name per instance of the window.
Furthermore, I need a one-to-one mapping between the WNDCLASS.lpfnWndProc
field and my Window subclass's (overridden) WndProc
method. This doesn't exac开发者_开发技巧tly work, though, if I'm forced to get the method pointer on a per-instance basis, since it breaks the entire OOP design and messes everything up.
While it's possible for me to enforce this consistency at run-time, it's a bit ugly, and so it's not a great solution.
So, long story short, does anyone have any idea of an elegant solution to the problem of creating an abstract static
method? I'm thinking of some design patterns like Factory and whatnot, but I'm not sure if they fit here... if someone thinks they might , I would really appreciate a little explanation on how it would fit into the design.
Thank you!
The standard solution for this is to give the base class two window procedures, a static one and a virtual one.
The base class registers its class with the static window procedure. The static window procedure then invokes the virtual window procedure. Many people omit the HWND
parameter from the virtual version since it can be obtained from the this
pointer, but I'll leave it in just to simplify the story.
class Window
{
public:
virtual LRESULT WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{ return DefWindowProc(hwnd, uMsg, wParam, lParam); }
private:
static LRESULT CALLBACK StaticWndProc(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_NCCCREATE) {
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(
reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams);
}
Window *pThis = reinterpret_cast<Window*>(
GetWindowLongPtr(hwnd, GWLP_USERDATA));
LRESULT lres = pThis ? pThis->WndProc(hwnd, uMsg, wParam, lParam)
: DefWindowProc(hwnd, uMsg, wParam, lParam);
}
};
Derived classes override Window::WndProc
.
The window proc doesn't have to be the same for all instances of a particular class name.
Typically, you'd use the RegisterClass
function to set the default window proc for windows of that class. When you create a new window that uses the class but you want it to override the default handling, you create the window and then call SetWindowLongPtr(hwnd, GWL_WNDPROC, newWndProc)
, where newWndProc
is a pointer to the new window's window proc.
The new window's window proc can then handle the messages that it wants to override, and call DefWindowProc
for the rest.
To construct a window with a new class name, have the constructor copy the class info for the inherited class and create a new class that's identical to that one, except for the name. Then expose a method that will allow the client to change class-specific things. The API functions you're interested in would be GetClassInfoEx
and SetClassLong
, SetClassWord
or SetClassLongPtr
.
Inheritance still makes sense in this world because the default processing is the same for every window of a particular class, and inherited classes inherit the default processing from their parents.
You don't need anything like an abstract static
method for this. All you need is the window handle. You can get the class name by calling GetClassName
with the window handle, and then call GetClassInfoEx
with the returned class name.
Wow, I certainly didn't expect this...
I searched for the phrase x86 thunks
on Google. And, as it turns out, I wasn't the first person to come across this problem (surprise!).
The first hit was the exact problem and the solution to this question about Window Procs, even though it had nothing to do with my query (or at least, very little to do directly)... hope someone else finds it useful too.
精彩评论