wtl学习总结
在windows平台,相比MFC,我更喜欢WTL,因其简洁漂亮。所以陆续花了一年的时间学习之,这里总结一下(在学习Wtl/Atl之前,最好是对WinApi编程有一定的了解)。
安装
Wtl主页 http://sourceforge.net/projects/wtl/ ,整个库就是一堆.h文件,官方没有提供Installer,下载后解压到某个目录即可。
如果需要在VS中使用“工作导向”,可以点击Appwiz目录下的对应js文件来安装之。虽然没有直接对VS2010的支持,不过拿VS2008的改改即可,详情Google下。
为了在VS中使用Wtl,可以将include目录添加到全局Include Path。不过如果仅仅希望对单个项目有效,则在添加C++ Include Path的同时,可能还需要添加资源的Include Path。如果不希望以来第三方的库,可以直接将源码放在项目内。
Atl
学习Wtl,不可能跳过Atl,Wtl底层就是使用Atl。关于Atl这里就几个点讲一下:
- ATL-style 模板
- 窗口创建和初始化
- Thunk技术
- 回调和消息绑定
ATL-style 模板
template <class T>
class B1
{
public:
void SayHi()
{
T* pT = static_cast<T*>(this); pT->PrintClassName();
}
protected:
void PrintClassName() { cout << "This is B1"; }
}; class D1 : public B1<D1>
{
// No overridden functions at all
};
使用这个模板形式有几个好处:
- 不需要使用指向对象的指针。
- 节省内存,因为不需要虚函数表。
- 因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数。
- 所有的函数调用在编译时确定(译者加:区别于C++的虚函数机制使用的动态编连),有利于编译程序对代码的优化。
http://www.winmsg.com/wtl/Part1.htm
窗口创建和初始化
有个哥们就这个流程分析了一下,地址在这里 http://blog.csdn.net/jznsmail/archive/2004/12/01/200947.aspx 。
在Windows下,任何窗口创建都是通过CreateWindowEx或者CreateWindow函数来实现之,Wtl/Atl也不例外,(不过Wtl和Atl的流程略有不同)。详细的流程见上面链接,这里就几个重点说一下(不仅仅是Atl,也包括Wtl的内容)。
对于Windows窗口,需要派生自CFrameWindowImpl类,创建和初始化窗口就在该类的CreateEx函数实现。通过前面讲到的“ATL-style 模板”,CreateEx会调用Create函数,CFrameWindowImpl提供了默认的Create实现,不过也可以在派生类中定制。在CFrameWindowImpl类成员函数Create中会首先注册窗口,然后才创建之。
在CFrameWindowImpl的Create函数中会通过CFrameWindowImplBase的成员函数Create创建实际的窗口,使用API函数CreateWindowEx。
在CFrameWindowImpl的Create函数中会调用CFrameWndClassInfo类成员函数Register注册窗口,注册用到的信息通过DECLARE_FRAME_WND_CLASS或DECLARE_FRAME_WND_CLASS_EX宏来指定。在这两个宏中会指定静态成员函数StartWindowProc 作为窗口回调。这个静态成员在CWindowImplBaseT类中定义。在这个静态成员中使用了下面提到的Thunk技术动态地修改回调参数,同时将回调重置为该类的另一个静态成员WindowProc(见API函数SetWindowLongPtr和属性GWLP_WNDPROC)。
以上提到的是Wtl流程的一个简单概述,对于普通窗口(控件),我们一般使用Atl的那一套而非Wtl。
对于控件,一般派生自CWindowImpl窗口,窗口工作在该类的成员函数Create中完成。注册窗口的信息使用DECLARE_WND_CLASS或DECLARE_WND_CLASS_EX宏。具体的注册操作在ATL::CWndClassInfo类的成员函数Register中完成,窗口的创建在CWindowImplBaseT类的成员函数Create中完成。
Thunk技术
对于Windows API,从系统的消息回调出来的消息唯一的标识符就是HWND句柄,而当前随便一个UI程序都有相当之多的控件,所以必须要有一种行之有效的方法来通过这个句柄定位控件。比较傻B的方法就是建立一个HWND到控件类实例的映射,然后在消息收到后查询之,不过这种方法有很大的局限性。
Atl用Thunk技术来处理这个问题,这个所谓的“Thunk技术”干的事挺简单,不过要理解代码还真不容易(至少我是花了不少功夫)。有兴趣可以研究下代码:
(Microsoft Visual Studio 9.0\VC\atlmfc\include\atlstdthunk.h)
#pragma pack(push,1)
struct _stdcallthunk
{
DWORD m_mov; // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
DWORD m_this; //
BYTE m_jmp; // jmp WndProc
DWORD m_relproc; // relative jmp
BOOL Init(DWORD_PTR proc, void* pThis)
{
m_mov = 0x042444C7; //C7 44 24 0C
m_this = PtrToUlong(pThis);
m_jmp = 0xe9;
m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));
// write block from data cache and
// flush from instruction cache
FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));
return TRUE;
}
//some thunks will dynamically allocate the memory for the code
void* GetCodeAddress()
{
return this;
}
void* operator new(size_t)
{
return __AllocStdCallThunk();
}
void operator delete(void* pThunk)
{
__FreeStdCallThunk(pThunk);
}
};
#pragma pack(pop)
这里我就不说什么原理了,我这水平也说不清:-)。
Thunk技术实际上就是用一段汇编代码将函数的参数动态的替换,具体来说是将回调函数参数HWND动态替换成窗口类实例的指针。关于它推荐阅读 http://www.cngr.cn/article/54/395/2006/2006071928301.shtml 。
这个过程在CWindowImplBaseT类的静态成员StartWindowProc中实现。首先在注册窗口时会指定它为回调,当第一次调用时,通过Thunk技术动态地修改回调参数,同时将回调重置为该类的另一个静态成员WindowProc。具体的实现代码如下:
pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
那这个类实例在哪获取呢?其实Wtl/Atl实现也并不美观,通过CAtlWinModule类存储这些指针,很显然整个程序只应该包含一个CAtlWinModule类实例,这也是为什么我们经常看到这个实例前带一个extern关键字。
在新的回调WindowProc中,Atl将参数HWND转型为CWindowImplBaseT类指针,然后调用该类的成员函数ProcessWindowMessage来进一步派发消息。这个函数怎么定义看下一节内容。
回调和消息绑定
前面提到一个ProcessWindowMessage函数,它最初在CMessageMap类中以纯虚函数的形式定义。在Atl中不管什么类,只要派生自CMessageMap就可以处理消息,这就提供了很大的灵活性。
ATL定义了一堆预处理宏来实现这个函数和分发逻辑,典型的如下:
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CLOSE, OnClose)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_ID_HANDLER(IDC_ABOUT, OnAbout)
END_MSG_MAP()
前后两个宏用于定义ProcessWindowMessage函数,中间则通过一个Switch结构来定义分发逻辑和回调绑定。
ATL这一套结构有几个好处:
- 避免过多的纯虚函数导致额外的开销
- 容易将消息处理重定向,例如将消息处理重定向到一个非窗口类,实现UI和逻辑分离。也可以让同一个消息处理类同时处理多个窗口的消息。具体见ATL定义的那堆宏。
Wtl在Atl基础上针对特定消息定义了很多更加精确的宏,不过个人不是很喜欢,因为懒得去记。
Wtl入门
最基本的Wtl程序可以通过Appwiz来生成,很有必要理解下Wtl程序的流程。简单地讲,控件使用CButton等封装类(atlctrls.h),窗口使用CFrameWindowImpl派生。如果希望自定义控件,可以派生自CWindowImpl(自绘还需要继承其他自绘支持类,见后面说明),例如
class CBeepButton: public CWindowImpl< CBeepButton,CButton >
{
public:
DECLARE_WND_CLASS( _T("CBeepButton"))
BEGIN_MSG_MAP( CBeepButton )
END_MSG_MAP()
};
不过上面的类封装除了装逼没有任何用处!
发现几个比较恶心的地方提一下。
如果在自定义控件有绘制字体时,绘出来的字很奇怪,和默认的字体相差较大,后来发现可以手动设置字体将其设置为UI默认字体。
m_widget_->SetFont(AtlGetStockFont(DEFAULT_GUI_FONT));
对于动态创建的Edit或者RichEdit,边框都很奇怪,为了设置默认的边框,需要
m_widget_->ModifyStyleEx(0, WS_EX_CLIENTEDGE, SWP_DRAWFRAME);
超类化
超类化(superclass )是一种生成新的窗口类的方法。它的中心思想是依靠现有的窗口类,克隆出另一个窗口类。被克隆的类可以是Windows预定义的窗口类,这些预定义的窗口类有按钮或下拉框控制等等。也可以是一般的类。克隆的窗口类使用被克隆的类(基类)的窗口消息处理函数。
克隆类可以有自己的窗口消息处理函数,也可以使用基类的窗口处理函数。
(http://www.builder.com.cn/2007/1116/637833.shtml )
超类化是以类型为单位来设置,也就是被超类的类必须是已经存在的窗口(很显然,如果想“超”自定义窗口类,必须保证该类至少被用过一次或者手动注册过,如果没注册则超类会初始化失败)。在消息处理时,Wtl首先会使用超类的处理函数,如果没处理,则会调用“被超类”的消息处理函数来处理。
那这个“被超类”的消息处理函数在哪里呢?毕竟每次只能注册一个回调函数。实际上在CWindowImplBaseT类中包含一个成员m_pfnSuperWindowProc用于在超类化时存储“被超类”的原始回调。这个成员在CFrameWndClassInfo类或_ATL_WNDCLASSINFOW结构的成员函数Register调用时作为参数传入实现初始化。
通过宏DECLARE_FRAME_WND_SUPERCLASS或DECLARE_WND_SUPERCLASS来声明一个超类,两者原理都是一致的,即定义一个OrigWndClassName。在注册窗口类是首先获得OrigWndClassName类的注册信息,然后替换回调,并且将原来的回调保存在参数中,这个参数即前面提到的m_pfnSuperWindowProc。
关于这两回调的配合,可以参考CWindowImplBaseT类静态成员函数WindowProc(这个函数即通过Thunk技术重置后的回调函数,见前面说明)。Wtl首先会调用当前的回调,如果返回FALSE,则继续调用原来的回调。
为了测试超类化自定义窗口类,我写了如下代码:
class CBeepButton1: public CWindowImpl< CBeepButton1,CButton >
{
public:
DECLARE_WND_CLASS( _T("CBeepButton1"))
BEGIN_MSG_MAP( CBeepButton1 )
MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )
MESSAGE_HANDLER( WM_LBUTTONUP, OnLButtonUp )
END_MSG_MAP() LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bHandled )
{
this->SetWindowText("Press");
bHandled = FALSE; // alternatively: DefWindowProc()
return 0;
} LRESULT OnLButtonUp( UINT, WPARAM, LPARAM, BOOL& bHandled )
{
this->SetWindowText("Release");
bHandled = FALSE; // alternatively: DefWindowProc()
return 0;
}
}; class CBeepButton: public CWindowImpl< CBeepButton >
{
public:
DECLARE_WND_SUPERCLASS( _T("BeepButton"), _T("CBeepButton1") )
BEGIN_MSG_MAP( CBeepButton )
MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )
END_MSG_MAP()
LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bHandled )
{
bHandled = FALSE; // alternatively: DefWindowProc()
return 0;
}
};
经测试发现几个问题,为了使用 CBeepButton,必须先实现一个 CBeepButton1(目的是为了注册窗口类,主要是手动注册不方便)。否则 CBeepButton1会注册失败。即便如此 CbeepButton还是无法运行,究其原因是因为Thunk技术未能重置回调。
前面说过,Atl在初始化一个窗口类时,会使用Thunk技术动态重置回调。这个两个回调分别是CWindowImplBaseT类静态成员函数StartWindowProc和WindowPro,Thunk技术在前者中被使用,是一个临时的回调,具体的消息分发在后者进行。
当超类化一个自定义窗口类时,首先会获得原窗口的回调,而这个回调很显然是StartWindowProc,而新的窗口类并非处理了所有消息,所以总是有部分消息会发送到原窗口类的回调,即StartWindowProc。杯具就在这里,StartWindowProc是一个临时的回调。实际中当消息派发到这里时会碰到断点,因为无法从CAtlWinModule中获得类实例的指针(这都不是该窗口类初始化过程,当然找不到了)。这不得不说是Wtl的一个Bug,不过就目前这种框架,要改好还真不容易。
子类化
子类化(subclass)是普遍采用的一种扩展窗口功能的方法。它的大致原理如下。
在一个窗口创建完了之后,将该窗口的窗口函数替换成新的窗口消息处理函数。这个新的窗口函数可以对某些需要处理的特定的消息进行处理,然后再将处理传给原来的窗口函数。
注意它与superclass的区别。
Superclass是以一个类为原版,进行克隆。既在注册新的窗口类时,使用的是基类窗口的窗口函数。
而subclass是在某一个窗口注册并创建后,通过修改该窗口的窗口消息函数的地址而实现的。它是针对窗口实例。
(http://www.builder.com.cn/2007/1116/637833.shtml )
子类化的核心就是前面提到的Thunk技术,这里就不废话了。
自绘
说的自绘,我真想吐血,很不明白微软为什么要为了自绘搞出那么一套鸟毛东西,虽然所有的自绘都可以通过Paint来完成,不过既然微软推出那么一套鸟毛,总是会要犹豫一下,”我用Paint来做会不会有什么不妥“。
并不是所有的内置控件都支持那一套鸟毛的自绘,Wtl通过COwnerDraw类和CCustomDraw 类来简化这一套东西的开发,其实就是定义一堆宏来处理回调,提供一个相对简单的接口给程序员。为了能够让控件收到这些自定义消息,需要在父窗口加入反射宏DEFAULT_REFLECTION_HANDLER()。
不过个人很少用这东西,即便要自绘Button,我也是通过WM_PAINT来做的(这种做法有什么不妥忘各位指出,不过我没发现效率有多低,至少结构一致和美观,我想谁都不想去理解那么多规则,简单就是美)。
浏览器控件
如今越来越多的UI程序选择浏览器控件来显示网页内容,这有很多好处,至少简化了开发并且可以实现很复杂的样式,更重要的是无需升级可保持最新。
Wtl官方并没有内置浏览器控件,不过可以参考这里http://devel.openocr.org/svn/openocr/trunk/cuneiform/interface/icrashreport/wtl/samples/tabbrowser/browserview.h ,
----------------------------------------------WTL 方式对话框数据交换(DDX)
MFC程序员的WTL指南: Part IV - 对话框与控件(二)
http://www.cnblogs.com/procoder/archive/2009/06/11/1501044.html
---wtl 界面开发 http://blog.163.com/l1_jun/blog/static/14386388201052922725417/
wtl学习总结的更多相关文章
- duilib底层机制剖析:窗体类与窗体句柄的关联
转载请说明原出处,谢谢~~ 看到群里朋友有人讨论WTL中的thunk技术,让我联想到了duilib的类似技术.这些技术都是为了解决c++封装的窗体类与窗体句柄的关联问题. 这里是三篇关于thunk技术 ...
- duilib底层机制剖析:窗口类与窗口句柄的关联
转载请说明原出处.谢谢~~ 看到群里朋友有人讨论WTL中的thunk技术,让我联想到了duilib的类似技术. 这些技术都是为了解决c++封装的窗口类与窗口句柄的关联问题. 这里是三篇关于thunk技 ...
- 闲聊桌面应用开发[Win16->Win32->ATL/WTL/MFC->WinForm->WPF/Silverlight/WinRT]
闲来无聊,正好小组人员讨论到桌面的开发,那把笔者接触的WIndows平台下的几个主要的发展过程聊一聊. 主要从概述,参考资料,图书等几个方面说起. 所有的界面开发都会涉及如下的几个方面的内容: v 控 ...
- Java学习-035-JavaWeb_004 -- JSP include 指令
inclue 指令是将不同的文件插入到 JSP 网页中,这些文件可以是文本文件.HTML文件.JSP 文件,指令语法如下: <%@include file="相对路径"%&g ...
- 微软WTL模板库完整版安装(VS2010+windows7X64位环境下)分享
一:简介 想必大家对于微软的MFC应该都比较熟悉.但是WTL可能很多人比较陌生吧.下面我就简单的说说这个库. 首先对这个库的做个简单的介绍吧. WTL 是 Windows Template Libra ...
- C++著名程序库的比较和学习经验 (转)
转自:http://www.open-open.com/lib/view/open1328670468108.html 内容目录: 1.C++各大有名库的介绍——C++标准库 2.C++各大有名库的介 ...
- STL,ATL,WTL之间的联系和区别
STL即 Standard Template Library (标准模板库) STL是惠普实验室开发的一系列软件的统称.它是由Alexander Stepanov.Meng Lee和David R M ...
- 照片浏览器软件-WTL开发的照片浏览器
前段时间,为了准备情人节礼物,本人想了做一个照片浏览器送给女友,专门播放我俩的所有照片的一个程序软件,于是,就写了这么一个照片浏览器软件.本程序是基于WTL8.0开发的一个图片/照片浏览器,涉及到XM ...
- WTL的核心机制
WTL背景介绍 WTL是微软ATL开发组成员Nenad Stefanovic先生在ATL Windowing机制上发展起来的一整套GUI框架,运用template技术组织和创建GUI对象,构筑了精致的 ...
随机推荐
- SOLR企业搜索平台 三 (schema.xml配置和solrj的使用)
标签:solrj 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://3961409.blog.51cto.com/3951409/8 ...
- .NET CORE 2.1 导出excel文件的两种方法
最近在做 MVC 项目的时候遇到项目的导出,下面总结下两种导出到excel 的方法 第一种方法: 将文件写到本地,然后返回这个File 或者返回这个 File 的绝对地址 其中 _hostingE ...
- MVC断点续传
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Mv ...
- java 通过ip获取客户端mac地址
java 通过ip获取客户端mac地址 package com.asppro.util; import java.io.BufferedReader; import java.io.IOExcepti ...
- c# ref与out的区别
c# ref与out的区别 相同点:都是输出参数 不同点: ref: 1.必须初始化,即:必须赋初始值: 2.有进有出: 3.用在需要被调用的方法修改调用者的引用的时候. 4.是传递参数的地址 o ...
- asp.net(mvc) 框架
1.NFine mvc+ef 2.Grove orm架构 3.NHibernate orm 4.NBear 5.petshop 6.Membership 7.Brnshop 网上商城 8.cms快速开 ...
- 动态添加select的option [转载]
动态给select标签添加option,结合前人经验以及自己经验,现在总结三种方法供大家参考,一起交流学习!首先是定义的select元素://根据ID获得select元素 var mySelect = ...
- 微信小程序的onLaunch()方法和onShow()方法
在app.js里面你会发现一个onLaunch()方法,这个方法是当小程序加载完毕后就执行的方法,此外,还有一个onShow()方法,先看下面的代码 app.js //app.js App({ onL ...
- Elasticsearch学习(3) spring boot整合Elasticsearch的原生方式
前面我们已经介绍了spring boot整合Elasticsearch的jpa方式,这种方式虽然简便,但是依旧无法解决我们较为复杂的业务,所以原生的实现方式学习能够解决这些问题,而原生的学习方式也是E ...
- 基于Java软引用机制最大使用JVM堆内存并杜绝OutOfMemory
题记:说好的坚持一周两篇文章在无数琐事和自己的懒惰下没有做好,在此表达一下对自己的不满并对有严格执行力的人深表敬意!!!! -------------------------------------- ...