
本文共 9373 字,大约阅读时间需要 31 分钟。
最近在使用C++封装了WindowsAPI来做程序开发。虽然有MFC库可以用,但是感觉还是不如API来的功能强大。有的时候还是要自己替换默认的窗口函数来实现特定的功能。
可是C++默认是面向对象的,直接把成员函数定义成窗口函数传递给SetWindowsLongptr会报错。搜了好久找到一篇文章
http://members.gamedev.net/sicrane/articles/WindowClass.html
为了防止以后链接过期先搬过来备忘。
Creating a C++ Window Class
Creating a C++ Window Class
One of the common urges that a C++ programmer starting Windows applications programming has is the desire to encapsulate the creation of a window and a window class in a C++ class. Unfortunately, this isn't a straightforward process. Usually the first sign of trouble is the inability to assign a normal C++ member function to the lpfnWndProc
member of the WNDCLASS
.
// usual first attemptclass MyWindowClass { public: LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); // other stuff}; // somewhere else WNDCLASS wc = {}; wc.lpfnWndProc = &MyWindowClass::WndProc; // error, incompatible pointer types
The reason for this problem is that non-static member functions have a different calling convention than normal functions. Member functions need to be aware of the this
pointer for the class, which normal function pointers cannot handle. So in order to use a normal member function to handle Windows messages you need to create a non-member or static member function that you can assign to lpfnWndProc
. That function then needs to figure out the right pointer for the HWND
that gets passed and then call the member function to handle the Windows message on that pointer.
There are a number of different schemes to associate a C++ object pointer with a handle. One method is to create some sort of map object that associates HWND
s with object pointers such as a std::map
or a hash table of some sort. This can be done either per thread or globally. The method I use in the code for this article is to associate the object pointer with the user data of the window instance.
As part of the call to CreateWindow()
or CreateWindowEx()
you can specify a pointer parameter as the lpParam
argument of the functions. When the WM_NCCREATE
and WM_CREATE
messages are sent to the window, the lpParam
argument of the CreateWindow()
or CreateWindowEx()
call is passed as the lpCreateParams
member of the CREATESTRUCT
. Since the WM_NCCREATE
is sent before WM_CREATE
, in the handler for WM_NCCREATE
I call SetWindowLongPtr()
to put that pointer in the user data portion of the window instance with the GWLP_USERDATA
flag. After that, all the message handler needs to do is grab the pointer from the user data with GetWindowLongPtr()
(again with the GWLP_USERDATA
flag) and call the right member function on the fetched pointer. However, WM_NCCREATE
usually isn't the first window message sent, so the static window procedure needs to account for that.
class MyWindowClass { public: void WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { if (LONG_PTR user_data = GetWindowLongPtr(hWnd, GWLP_USERDATA)) { MyWindowClass * this_window = reinterpret_cast(user_data); return this_window->WndProc(hWnd, Msg, wParam, lParam); } if (Msg == WM_NCCREATE) { LPCREATESTRUCT create_struct = reinterpret_cast (lParam); void * lpCreateParam = create_struct->lpCreateParams; MyWindowClass * this_window = reinterpret_cast (lpCreateParam); SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast (this_window)); return this_window->WndProc(hWnd, Msg, wParam, lParam); } return DefWindowProc(hWnd, Msg, wParam, lParam); }}; // somewhere else WNDCLASS wc = {}; wc.lpfnWndProc = &MyWindowClass::StaticWndProc;
To simplify the logic used by the static window procedure function, I use two windows procedures. The first one's job is to wait until WM_NCCREATE
is called and then place the pointer in the user data of the window instance. Once it does that, it changes the windows procedure to the second window procedure, which only takes the pointer from the user data and calls the member function that handles the actual messages. It does this with SetWindowLongPtr()
and the GWLP_WNDPROC
flag.
class MyWindowClass { public: void WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK InitialWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { if (Msg == WM_NCCREATE) { LPCREATESTRUCT create_struct = reinterpret_cast(lParam); void * lpCreateParam = create_struct->lpCreateParams; MyWindowClass * this_window = reinterpret_cast (lpCreateParam); SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast (this_window)); SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast (&MyWindowClass::StaticWndProc)); return this_window->WndProc(hWnd, Msg, wParam, lParam); } return DefWindowProc(hWnd, Msg, wParam, lParam); } static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { LONG_PTR user_data = GetWindowLongPtr(hWnd, GWLP_USERDATA); MyWindowClass * this_window = reinterpret_cast (user_data); return this_window->WndProc(hWnd, Msg, wParam, lParam); }}; // somewhere else WNDCLASS wc = {}; wc.lpfnWndProc = &MyWindowClass::InitialWndProc;
This may not look simpler, but it reduces the responsiblity of the static window procedure, which in turn translates into better branch prediction for the CPU.
Source code to a complete sample application is available .
Appendix: Generic Text Mappings
The sample application presented utilizes Microsoft's generic text mappings. Most Windows API functions and structures come in two versions: a narrow character version and a wide character version. These are also known as ANSI and Unicode versions. For example,CreateWindow()
is actually two functions: CreateWindowA()
and CreateWindowW()
. A macro transformation maps CreateWindow()
calls to one of these two functions depending on what preprocessor definitions are in effect. If UNICODE
is defined, CreateWindow()
is actually CreateWindowW()
. If it isn't defined CreateWindow()
is actually CreateWindowA()
.
The difference between the two versions is that the ANSI versions use CHAR
s for their character type in strings. The wide character versions use WCHAR
s for their character types. In order to write code that compiles cleanly with both UNICODE
defined and it not defined, you can use the generic text mappings. The type generic text mappings are a series of macros that, like CreateWindow()
and other Windows API functions and structures, change their definition based upon whether or not UNICODE
is defined.
For example, the macro TCHAR
changes between CHAR
or WCHAR
or LPCTSTR
is either LPCSTR
or LPCWSTR
. Wrapping string and character literals with the TEXT()
or _T()
macros will create narrow character literals when UNICODE
isn't defined and wide character literals when it is. To use the generic text mappings, you first use these types and macros instead of using types like CHAR
or LPCSTR
and you need to include the tchar.h
header.
However, the process isn't completely transparent. There are several places where the generic text mappings break down. For instance, the transformations are not defined on C++ standard library classes and functions. In some places you can use the template parameterization of the C++ standard library classes to your advantage. For example, in the sample program I use the typedef:
typedef std::basic_stringstreamtstringstream;
This is equivalent to a std::stringstream
when UNICODE
isn't defined and a std::wstringstream
when it is. You can define similar typedefs for std::basic_string
but that abstraction breaks down for other types such as the file stream classes, which have functions that require narrow character string arguments and the exception classes which also only take narrow character string arguments, hence why there are non _T()
or TEXT()
wrapped string literals used as exception constructor arguments in the source code. Fortunately the C++ ostream classes provides for automatic widening when a narrow chararacter string is inserted into a wide character ostream.
Most compiler by default do not define the UNICODE
preprocessor symbol. However, Microsoft Visual C++ .NET 2005 does define UNICODE
by default.
原理是普通成员函数在编译以后是一种特殊的函数,因为他要维护this指针。而static类型的成员函数没有this指针可以传递给SetWindowLongPtr作为参数使用。可是没了this指针,要访问类的成员怎么办? 有一种方法就是SetWindowLongPtr 的时候使用GWLP_USERDATA 可以把自定义的数据类型指针传递给他,之后在static函数里使用GetWindowLongPtr 可以取出这个数据。有了这种方法就可以很方便的使用C++的成员函数来实现windows的窗口函数了。
// Save the this pointer (AP_Win32Dialog_EditControlProp) to the edit control's user data. SetWindowLongPtrW(GetDlgItem(hWnd, AP_RID_DIALOG_CONTROL_PROP_EDIT_DISPLAY_TEXT), GWL_USERDATA, reinterpret_cast(this)); // Register the window proc for the Edit control. OldEditProc = (WNDPROC)SetWindowLongPtrW(GetDlgItem(hWnd, AP_RID_DIALOG_CONTROL_PROP_EDIT_DISPLAY_TEXT), GWL_WNDPROC, reinterpret_cast (&AP_Win32Dialog_EditControlProp::EditProc));
plus: 追记在使用SetWindowLongPtr修改默认的注意使用SetWindowLongPtr的版本是Unicode还是ANSI 他必须和窗口句柄的默认编码相同,否则会出现奇怪的乱码问题。比如Edit控件本身是UNICODE内码,若使用ANSI版本的SetWndowLong把其默认的窗口函数替换了,之后会发现所有输入的中文字符都是乱码。所以这里要特别注意!!!
发表评论
最新留言
关于作者
