我正在用 D 语言为 Windows 编写一个面向对象的窗口 API 包装器,并且我遇到了(非特定于语言的)设计问题。
Windows 要求所有窗口都事先用
RegisterClass
注册;扩展现有类需要替换窗口过程。此外,似乎有两种窗口句柄:需要处理的 HWND
(通过 DestroyWindow
)和不需要的 HWND
(例如来自其他应用程序)。
我创建了一个
Window
类,只要我包装 ShowWindow
、UpdateWindow
、FindWindow
和其他此类方法,一切都很好。但是,当我尝试向调用 className
的类添加带有 CreateWindow
参数的构造函数时,我遇到了一个问题:
className
必须已由RegisterClass
注册。Window
的子类在尝试直接或间接创建新窗口之前以某种方式调用 RegisterClass
。Window
子类,所有实例实际上都是同一窗口类的实例;也就是说,来自特定子类的所有 className
都是相同的。 (这是因为特定窗口类的所有实例的窗口过程需要相同。)问题是,没有办法有
abstract static
方法(以便 Window 能够向子类询问其类信息,并注册一次),所以我被迫说类似 CreateWindow(this.className, ...)
之类的话
为了创建一个新窗口,如果我的子类不遵守此规则,这很容易出现问题,并为每个窗口实例提供不同的类名。
此外,我需要
WNDCLASS.lpfnWndProc
字段和我的 Window 子类的(重写)WndProc
方法之间的一对一映射。但是,如果我被迫在每个实例的基础上获取方法指针,这并不完全有效,因为它破坏了整个 OOP 设计并搞乱了一切。
虽然我可以在运行时强制这种一致性,但这有点难看,所以这不是一个很好的解决方案。
那么,长话短说,有谁知道有什么优雅的解决方案来解决创建
abstract static
方法的问题吗?我正在考虑一些设计模式,例如工厂之类的,但我不确定它们是否适合这里......如果有人认为它们可能,我真的很感激关于它如何适合设计的一些解释。
谢谢!
对此的标准解决方案是为基类提供两个窗口过程,一个静态的和一个虚拟的。
基类用静态窗口过程注册它的类。然后静态窗口过程调用虚拟窗口过程。许多人在虚拟版本中省略了
HWND
参数,因为它可以从 this
指针获取,但我将其保留只是为了简化故事。
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);
}
};
派生类覆盖
Window::WndProc
。
哇,我当然没想到会是这样......
我在谷歌上搜索了短语
x86 thunks
。而且,事实证明,我并不是第一个遇到这个问题的人(惊讶!)。
第一个命中是关于 Window Procs 的问题的确切问题和解决方案,即使它与我的查询无关(或者至少,直接做的很少)...希望其他人找到也很有用。
对于特定类名的所有实例,窗口过程不必相同。
通常,您可以使用
RegisterClass
函数为该类窗口设置 default 窗口过程。 当您创建一个使用该类的新窗口但希望它覆盖默认处理时,您可以创建该窗口,然后调用 SetWindowLongPtr(hwnd, GWL_WNDPROC, newWndProc)
,其中 newWndProc
是指向新窗口的窗口过程的指针。
新窗口的窗口过程可以处理它想要覆盖的消息,并调用
DefWindowProc
来处理其余的事情。
要使用新类名构造一个窗口,请让构造函数复制继承类的类信息,并创建一个与该类相同(名称除外)的新类。 然后公开一个方法,允许客户端更改特定于类的内容。 您感兴趣的 API 函数是
GetClassInfoEx
和 SetClassLong
、SetClassWord
或 SetClassLongPtr
。
继承在这个世界上仍然有意义,因为默认处理对于特定类的每个窗口都是相同的,并且继承的类从其父类继承默认处理。
您不需要像
abstract static
之类的方法来实现此目的。您所需要的只是窗把手。 您可以通过使用窗口句柄调用 GetClassName
来获取类名,然后使用返回的类名调用 GetClassInfoEx
。