duilib底层机制剖析:窗口类与窗口句柄的关联
转载请说明原出处。谢谢~~
看到群里朋友有人讨论WTL中的thunk技术,让我联想到了duilib的类似技术。
这些技术都是为了解决c++封装的窗口类与窗口句柄的关联问题。
这里是三篇关于thunk技术的博客,不懂的朋友能够先看一下:
WTL学习之旅(三)WTL中 Thunk技术本质(含代码)
深入剖析WTL—WTL框架窗体分析 (5)
学习下 WTL 的 thunk
我这里直接引用其它博客的一部分文字来说明窗口类与窗口句柄关联的重要性和相关的问题,然后说明一下duilib中的解决方法:
-----------------------------------------------------引用開始------------------------------------------------------------------
因为 C++ 成员函数的调用机制问题,对C语言回调函数的 C++ 封装是件比較棘手的事。为了保持C++对象的独立性。理想情况是将回调函数设置到成员函数。而一般的回调函数格式一般是普通的C函数,尤其是 Windows API 中的。
好在有些回调函数中留出了一个额外參数,这样便能够由这个通道将 this 指针传入。比方线程函数的定义为:
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;
这样,当我们实现线程类的时候。就能够:
class Thread
{
private:
HANDLE m_hThread;
public:
BOOL Create()
{
m_hThread = CreateThread(NULL, 0, StaticThreadProc, (LPVOID)this, 0, NULL);
return m_hThread != NULL;
}
private:
DWORD WINAPI ThreadProc()
{
// TODO
return 0;
}
private:
static DWORD WINAPI StaticThreadProc(LPVOID lpThreadParameter)
{
((Thread *)lpThreadParameter)->ThreadProc();
}
};
只是,这样,成员函数 ThreadProc() 便丧失了一个參数,这通常无伤大雅。不论什么原本须要从參数传入的信息都能够作为成员变量让 ThreadProc 来读写。假设一定有些什么是非从參数传入不可的,那也能够。一种做法。创建线程的时候传入一个包括 this 指针信息的结构。
另外一种做法,对该 class 作单例限制——假设现实情况同意的话。
所以,有额外參数的回调函数都优点理。不幸的是,Windows 的窗体回调函数没有这样一个额外參数:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
这使得对窗体的 C++ 封装变得困难。
为了解决问题。一个非常自然的想法是,维护一份全局的窗体句柄到窗体类的相应关系,如:
#include <map>
class Window
{
public:
Window();
~Window();
public:
BOOL Create();
protected:
LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);
protected:
HWND m_hWnd;
protected:
static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
static std::map<HWND, Window *> m_sWindows;
};
在 Create 的时候,指定 StaticWndProc 为窗体回调函数。并将 hWnd 与 this 存入 m_sWindows:
BOOL Window::Create()
{
LPCTSTR lpszClassName = _T("ClassName");
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.lpfnWndProc = StaticWndProc;
wcex.hInstance = hInstance;
wcex.lpszClassName = lpszClassName;
RegisterClassEx(&wcex);
m_hWnd = CreateWindow(lpszClassName, NULL, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (m_hWnd == NULL)
{
return FALSE;
}
m_sWindows.insert(std::make_pair(m_hWnd, this));
ShowWindow(m_hWnd, SW_SHOW);
UpdateWindow(m_hWnd);
return TRUE;
}
在 StaticWindowProc 中,由 hWnd 找到 this,然后转发给成员函数:
LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
std::map<HWND, Window *>::iterator it = m_sWindows.find(hWnd);
assert(it != m_sWindows.end() && it->second != NULL);
return it->second->WndProc(message, wParam, lParam);
}
(m_sWindows 的多线程保护略过,下同)
据说 MFC 採用的就是类似的做法。
缺点是,每次 StaticWndProc 都要从 m_sWindows 中去找 this。因为窗体类通常会保存窗体句柄,回调函数里的 hWnd 就没多大作用了,假设这个 hWnd 可以被用来存 this 指针就好了,那么就能写成这样:
LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return ((Window *)hWnd)->WndProc(message, wParam, lParam);
}
这样看上去就爽多了。传说中 WTL 所採取的 thunk 技术就是这么干的。
-----------------------------------------------------引用结束------------------------------------------------------------------
能够看到,封装一个窗口类,让这个类与他生成的窗口关联,而且去处理这个窗口的窗口消息并非简单的事。MFC和WTL都有自己的方法来解决。
而duilib库的最初作者更是对MFC、WTL等库相当熟悉,我这里说明一下duilib解决问题的办法。个人认为duilib的这个办法要比thunk简单好用非常多。
我们使用duilib创建一个窗口,会调用窗口基类CWindowWnd类的Create函数,相关代码例如以下:
HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
{
if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
ASSERT(m_hWnd!=NULL);
return m_hWnd;
}
能够看到终于使用了CreateWindowEx函数来创建窗口。而这里的最后一个參数相当关键。这里是CreateWindowEx函数让我们自己传递的一个自己定义数据,能够看到duilib中把自己类的this传了进去。这就是duilib解决窗口类与窗口句柄关联的起点了。
接着当窗口開始建立时就会发送消息到相关的消息处理回调函数,duilib中相应的是__WndProc函数,函数代码例如以下:
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowWnd* pThis = NULL;
if( uMsg == WM_NCCREATE ) {
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
pThis->m_hWnd = hWnd;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
}
else {
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( uMsg == WM_NCDESTROY && pThis != NULL ) {
LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
if( pThis->m_bSubclassed ) pThis->Unsubclass();
pThis->m_hWnd = NULL;
pThis->OnFinalMessage(hWnd);
return lRes;
}
}
if( pThis != NULL ) {
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
我们一般会理解在窗体创建时发出消息WM_CREATE,可是在WM_CREATE消息之前另一个消息是被发出的。那就是WM_NCCREATE消息,能够看到在duilib处理函数中环绕这个消息做了文章。先看看这个消息的介绍:
Parameters
- wParam
-
This parameter is not used.
- lParam
-
A pointer to the CREATESTRUCT structure
that contains information about the window being created. The members of CREATESTRUCT are identical to the parameters of the CreateWindowEx function.
这个消息的lParam參数是关键。这个參数是传进来CREATESTRUCT结构,这个结构体介绍例如以下:
CREATESTRUCT 结构定义初始化參数传递给应用程序的窗体过程。
typedef struct tagCREATESTRUCT {
LPVOID lpCreateParams;
HANDLE hInstance;
HMENU hMenu;
HWND hwndParent;
int cy;
int cx;
int y;
int x;
LONG style;
LPCSTR lpszName;
LPCSTR lpszClass;
DWORD dwExStyle;
} CREATESTRUCT;
- lpCreateParams
-
将与要使用数据的点创建一个窗体。
- hInstance
-
识别模块拥有新窗体模块的实例句柄。
- hMenu
-
标识新窗体将使用菜单。
子窗体。假设包括整数 ID.
- hwndParent
-
标识拥有新窗体的窗体。 新窗体。假设是顶级窗体,该成员是 NULL。
- cy
-
指定窗体的新高度。
- cx
-
指定窗体的新宽度。
- y
-
指定新窗体左上角的 y 坐标。 假设新窗体是子窗体。坐标系是相对于父窗体;否则是相对于屏幕坐标原点。
- x
-
指定新窗体左上角的 x坐标。 假设新窗体是子窗体,坐标系是相对于父窗体;否则是相对于屏幕坐标原点。
- style
-
指定新窗体中 style。
- lpszName
-
为指定新窗体的名称以 NULL 结尾的字符串的位置。
- lpszClass
-
为指定新窗体的窗体类名的 null 终止的字符串的结构。WNDCLASS (点有关很多其它信息,请參见 Windows SDK。)
- dwExStyle
-
对于新窗体指定 扩展样式。
能够看到这个结构体的第一个參数正是在CreateWindowEx函数传入的自己定义数据,也就是窗口类的this指针。duilib接下来通过这个结构体获取到窗口类的指针,并使其m_hWnd成员变量赋值为窗口的句柄。接着把这个这个指针通过SetWindowLongPtr函数与窗口句柄关联了起来!
然后能够看到假设处理的不是WM_NCCREATE消息,就是用GetWindowLongPtr函数通过窗口句柄获取到窗口类的指针,再去调用相关的消息处理函数。duilib使用这种方法巧妙的将窗口类和窗口句柄关联起来,而没有像WTL的thunk技术那么麻烦。在使用duilib的时候,我们相同能够使用GetWindowLongPtr函数直接从窗口布局获取到窗口类指针,这可能会在处理某些事情的时候有妙用!
假设文章中有什么错误,能够联系我或者留言
Redrain QQ:491646717 2014.9.19
duilib底层机制剖析:窗口类与窗口句柄的关联的更多相关文章
- duilib底层机制剖析:窗体类与窗体句柄的关联
转载请说明原出处,谢谢~~ 看到群里朋友有人讨论WTL中的thunk技术,让我联想到了duilib的类似技术.这些技术都是为了解决c++封装的窗体类与窗体句柄的关联问题. 这里是三篇关于thunk技术 ...
- ATL7窗口类详细剖析
前言: ATL是微软继MFC之后提供的一套C++模板类库,小巧.精妙.效率极高.它的主要作用是为我们编写COM/DOM/COM+程序提供了丰富的支持.但是ATL只能写COM么?我以前只是MFC程序员的 ...
- MFC注册窗口类以及FindWindow按窗口类名查询(避免用#32770获取窗口句柄)
呵呵,最近在研究SendMessage函数,其中需要用到m_hWnd,之后延伸着又尝试获得窗口的句柄,于是遇到了FindWindow函数,原型如下: HWND FindWindow ( LPCSTR ...
- Duilib中系统消息在自己窗口类的使用
这些Win32消息响应函数,子类只需要重写,不需要在HandleMessage里面再调用一次 开发中遇到的问题,在任务栏关闭程序,会响应WM_SYSCOMMAND消息,因为要给用户提示是否关闭,所以需 ...
- 窗口的子类化与超类化——子类化是窗口实例级别的,超类化是在窗口类(WNDCLASS)级别的
1. 子类化 理论:子类化是这样一种技术,它允许一个应用程序截获发往另一个窗口的消息.一个应用程序通过截获属于另一个窗口的消息,从而实现增加.监视或者修改那个窗口的缺省行为.子类化是用来改变或者扩展一 ...
- MFC DestroyWindow窗口对象和窗口句柄的销毁
考虑单窗口情况: 假设自己通过new创建了一个窗口对象pWnd,然后pWnd->Create.则销毁窗口的调用次序: 1. 手工调用pWnd->DestroyWindow(): 2. De ...
- C#进阶系列——WebApi 路由机制剖析:你准备好了吗?
前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分. 它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home/Index,那么你只需要配置一个默认路由就能简单搞定: ...
- Android的消息循环机制 Looper Handler类分析
Android的消息循环机制 Looper Handler类分析 Looper类说明 Looper 类用来为一个线程跑一个消息循环. 线程在默认情况下是没有消息循环与之关联的,Thread类在ru ...
- 窗口类(Window Class)概述
windows窗口编程(通常意义上的win32)有几个比较核心的概念:入口函数WinMain.窗口类Window Class.窗口过程.消息处理机制.通用控件.本文主要介绍窗口类的相关概念,包括: 窗 ...
随机推荐
- Html网页表格结构化标记的应用
在讲网页表格的结构化标记之前,还是先看几幅图片. Html表格的结构化 所谓的结构化,正如上述第一副图所看到的,就是把我们的表格划分为三种:表头.表体.表尾.从而当我们在改动表体部分的时候,不会影响到 ...
- Swift - 项目部署配置(支持的系统,设备和状态条样式等)
点击项目,在General选项卡中的“Deployment Info”栏目中可以进行一些项目的配置 Deployment Target:支持的iOS SDK的最低版本 Device:所支持的设备(iP ...
- 每天一个JavaScript实例-推断图片是否载入完毕
<!doctype html> <html lang="en"> <head> <meta charset="utf-8&quo ...
- ALV调用的几个标准函数
ALV的调用主要由以下几个标准函数实现,所有函数的输入输出参数必须大写,否则系统会出现异常中止,相关函数如下: 1)REUSE_ALV_FIENDCATALOG_MERGE:根据内表结构返回FIELD ...
- 可以根据柜子内表取出所有的柜子信息的BAPI函数
DATA: gt_hunumbers TYPE STANDARD TABLE OF bapihunumber, gt_huitem TYPE STANDARD TABLE OF bapih ...
- 这里的*号实际表示就是RAC中所有实例都使用
您的位置: ITPUB个人空间 » cc59的个人空间 » 日志 发布新日志 我的日志我的足迹我的收藏 unix/linuxHA随笔backup&restoreperformance tuni ...
- OpenJDK1.8.0 源码解析————HashMap的实现(一)
HashMap是Java Collection Framework 的重要成员之一.HashMap是基于哈希表的 Map 接口的实现,此实现提供所有可选的映射操作,映射是以键值对的形式映射:key-v ...
- hdu 4715
#include<stdio.h> #include<string.h> int prime[1100000],p[1000000],ans; void pri() { ...
- mfc 导出数据保存成excel和txt格式
最近做了一些东西,项目到了收尾的工作.不过这次我没有参与到控件机器的功能的那一部分,都是主管自己写的.不过,所有的控件重写都是由我来做的.还有数据库这一方面是我和主管共同完成的.不过还不错,主管写一部 ...
- Android图片与旋转
拍照后的照片有时被系统旋转,纠正过程例如以下: 1.先读取图片文件被旋转的角度: /** * 通过ExifInterface类读取图片文件的被旋转角度 * @param path : 图片文件的路径 ...