使用C++类的成员函数来做windows的窗口函数

来源:互联网 发布:京东面试题java 编辑:程序博客网 时间:2024/06/11 19:38

最近在使用C++封装了WindowsAPI来做程序开发。虽然有MFC库可以用,但是感觉还是不如API来的功能强大。有的时候还是要自己替换默认的窗口函数来实现特定的功能。

可是C++默认是面向对象的,直接把成员函数定义成窗口函数传递给SetWindowsLongptr会报错。搜了好久找到一篇文章

http://members.gamedev.net/sicrane/articles/WindowClass.html

为了防止以后链接过期先搬过来备忘。


Creating a C++ Window Class 
by Howard "SiCrane" Jeng

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 HWNDs 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<MyWindowClass *>(user_data);        return this_window->WndProc(hWnd, Msg, wParam, lParam);      }      if (Msg == WM_NCCREATE) {        LPCREATESTRUCT create_struct = reinterpret_cast<LPCREATESTRUCT>(lParam);        void * lpCreateParam = create_struct->lpCreateParams;        MyWindowClass * this_window = reinterpret_cast<MyWindowClass *>(lpCreateParam);        SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(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<LPCREATESTRUCT>(lParam);        void * lpCreateParam = create_struct->lpCreateParams;        MyWindowClass * this_window = reinterpret_cast<MyWindowClass *>(lpCreateParam);        SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this_window));        SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&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<MyWindowClass *>(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 here.

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 CHARs for their character type in strings. The wide character versions use WCHARs 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_stringstream<TCHAR> tstringstream;

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<LONG_PTR>(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<LONG_PTR>(&AP_Win32Dialog_EditControlProp::EditProc));



plus: 追记在使用SetWindowLongPtr修改默认的注意使用SetWindowLongPtr的版本是Unicode还是ANSI 他必须和窗口句柄的默认编码相同,否则会出现奇怪的乱码问题。比如Edit控件本身是UNICODE内码,若使用ANSI版本的SetWndowLong把其默认的窗口函数替换了,之后会发现所有输入的中文字符都是乱码。所以这里要特别注意!!!

0 0