学习WTL可以有多种方式,当然如果有COM和ATL的知识背景最好不过,如果你有MFC编程背景却最为糟糕,除非你对MFC无所不知、无所不能: -)(如果你不是MFC的ORACLE,那么最好忘却它)
本系列打算从Win32和ATL入手,来学习WTL,情况理想的话的可以做到一举四得: Win32、ATL、WTL、C++(OO和泛型编程)
WTL的文档相对较少,且有些文档多是针对WTL3.0和WTL7.1,相对于最新的WTL8.0有些人老珠黄,也些范式已不太常用了,这也是促使写本系列的最初想法,帮助自己也帮助和我有同样需要的人,如果可以的话。
Part-1主要是从Win32和ATL着手,来讲解Windows编程的基本概念,有Win32和ATL基础的可以略过。: -)
1. “Hello World!” in Win32
1.1创建一个Win32 Project
当然创建过程相当简单,主要目的是为了了解一下Win32程序的基本结构并为写第一个ATL程序作准备。
在WndProc中:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
// TODO:标签下添加TextOut(hdc, 0, 0, _T("Hello world!"), 12);语句。
有3点需要注意的,请按图示:
Figure-1.1-1
Figure-1.1-2
采用默认设置。
Figure-1.1-3
将C/C++ -> Advanced -> Compile As 设为Compile as C Code(/TC), 这点很容易被忽视,如果你要写纯血统M$ C而又不想过多的关注C/C++文件扩展名的话;如果,你设置了/TC编译器选项,请将
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
VS2005向导产生的Win32 Project的块结构(block)和VS2003已有较大的改进了: -)。
以下代码均为了更清楚地说明主要问题而剔除了无关紧要的部分。
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
MSG msg;
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
以上代码结构主要由3个块构成:Register windows class、Initialize application和Main message loop 。
1.2.1 Register windows class
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WIN32);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
1.2.3 Initialize application
主要为CreateWindow、ShowWindow 。
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
return TRUE;
}
在这里,主要为处理WM_PAINT和WM_DESTROY消息
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
TextOut(hdc, 0, 0, _T("Hello world!"), 12);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
1.3 Build与程序输出
在
1.1创建的Win32代码中移除或注释掉stdafx.h中的:
// Windows Header Files:
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
并在stdafx.h中添加如下的include语句:
#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>
#include <atlwin.h>
#include "CWellcomeWindow.h"
2.2 添加CWellcomeWindow.h
#pragma once
#include "stdafx.h"
class CWellcomeWindow : public CWindowImpl<CWellcomeWindow,
CWindow, CFrameWinTraits> {
public:
DECLARE_WND_CLASS(NULL);
BEGIN_MSG_MAP(CWellcomeWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
HICON appIcon = LoadIcon(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDI_LEARNINGWTLPART1_ATL));
this->SetIcon(appIcon);
return 0;
}
LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
PAINTSTRUCT ps;
CComBSTR wellcome(_T("Hello World!"));
HDC hdc; // = this->GetDC();
hdc = this->BeginPaint(&ps); // this->BeginPaint(&ps);
TextOut(hdc, 0, 0, wellcome, wellcome.Length());
this->EndPaint(&ps);
//this->ReleaseDC(hdc);
return 0;
}
LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
::PostQuitMessage(0);
return 0;
}
};
保留WinMain方法声明和#include "stdafx.h"语句将其余代码删除或注释掉。添加_Module定义,修改WndMain方法体;完成后的代码如下:
#include "stdafx.h"
#include "LearningWTLPart1_ATL.h"
CComModule _Module;
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
_Module.Init(NULL, hInstance);
CComBSTR appTitle;
appTitle.LoadString(_Module.GetResourceInstance(), IDS_APP_TITLE);
CWellcomeWindow wnd;
wnd.Create(NULL, 0, appTitle);
// Main message loop:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
_Module.Term();
return (int) msg.wParam;
}
程序输出如下:
首先,对Win32编程模型作了一个初步介绍,着重介绍ATL编程模型,这里并不存在对二者有孰优孰劣的假设;由于,对于作为M$ Windows程序员来说,或多或少、或直接或间接得都接触过Win32编程模型,故在本节中轻彼而重此。
其次,对Win32程序和ATL程序的Build过程和产生的可执行文件作一个初步的对照。
Win32编程是整个Windows编程的基石,无论是ATL或WTL;因此,即使是ATL也必然包含Win32编程模型的基础构造块,只是ATL提供了轻量的基础构造块的封装(Encapsulation)。
Win32编程模型的基础构造块主要由Register windows class、Create window/Show window、Message loop和WndProc 4部分构成。
3.1.1 Win32基础构造块和主要流程
- CComModule::Init & CComModule::Term : 是ATL关于COM Server的部分,在这里不必关注,在后续的WTL学习中还会相应地涉及到;
- Main message loop : 在ATL中保留;
对Win32版与ATL版的Debug和Release分别作一个初步的对比;为说明一般性问题,在此不考虑编译器优化,因此,无论是Debug抑或Release均采用编译器默认设置。
|
Debug |
Release |
比重 |
Win32版 |
100K |
68K |
|
ATL版 |
244K |
88K |
D244% | R129% |
ATL版的Release,无论是物理文件大小还是运行时空间大小或运行效率都与Win32版的Release接近,且二者除系统库外,基本上不需要其它支持库(可能msvcrt.dll,gdi32.dll等)。
在
1.2.1 Register windows class 中,MyRegisterClass函数要完成的工作:1. 初始化WNDCLASSEX;2. RegisterClassEx 。 而在ATL中,由
CWellcomeWindow之由宏定义的成员函数(member function by macro defined)
DECLARE_WND_CLASS(NULL)完成,其由
wnd.Create(NULL, 0, appTitle) 调用(例如,在win32.cpp中)。
/////////////////////////////////////////////////////////////////////////////
// CWndClassInfo - Manages Windows class information
#define DECLARE_WND_CLASS(WndClassName) \
static ATL::CWndClassInfo& GetWndClassInfo() \
{ \
static ATL::CWndClassInfo wc = \
{ \
{ sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, StartWindowProc, \
0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClassName, NULL }, \
NULL, NULL, IDC_ARROW, TRUE, 0, _T("") \
}; \
return wc; \
}
4.1.2 CWndClassInfo结构(_ATL_WNDCLASSINFOW)定义
struct _ATL_WNDCLASSINFOW
{
WNDCLASSEXW m_wc;
LPCWSTR m_lpszOrigName;
WNDPROC pWndProc;
LPCWSTR m_lpszCursorID;
BOOL m_bSystemCursor;
ATOM m_atom;
WCHAR m_szAutoName[5+sizeof(void*)*CHAR_BIT];
ATOM Register(WNDPROC* p)
{
return AtlWinModuleRegisterWndClassInfoW(&_AtlWinModule, &_AtlBaseModule, this, p);
}
};
HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,
DWORD dwStyle = 0, DWORD dwExStyle = 0,
_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)
{
if (T::GetWndClassInfo().m_lpszOrigName == NULL)
T::GetWndClassInfo().m_lpszOrigName = GetWndClassName();
ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);
dwStyle = T::GetWndStyle(dwStyle);
dwExStyle = T::GetWndExStyle(dwExStyle);
// set caption
if (szWindowName == NULL)
szWindowName = T::GetWndCaption();
return CWindowImplBaseT< TBase, TWinTraits >::Create(hWndParent, rect, szWindowName,
dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);
}
真正完成CreateWindow。
Main message loop在ATL予以了保留(例如,在win32.cpp中)。
BEGIN_MSG_MAP(CWellcomeWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
#define BEGIN_MSG_MAP(theClass) \
public: \
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) \
{ \
BOOL bHandled = TRUE; \
(hWnd); \
(uMsg); \
(wParam); \
(lParam); \
(lResult); \
(bHandled); \
switch(dwMsgMapID) \
{ \
case 0:
#define END_MSG_MAP() \
break; \
default: \
ATLTRACE(ATL::atlTraceWindowing, 0, _T("Invalid message map ID (%i)\n"), dwMsgMapID); \
ATLASSERT(FALSE); \
break; \
} \
return FALSE; \
}
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0)
{
BOOL bHandled = TRUE;
(hWnd);
(uMsg);
(wParam);
(lParam);
(lResult);
(bHandled);
switch(dwMsgMapID)
{
case 0:
if(uMsg == msg)
{
bHandled = TRUE;
lResult = OnCreate(uMsg, wParam, lParam, bHandled);
if(bHandled)
return TRUE;
}
……
break;
default:
break;
}
return FALSE;
}
对于一些技术点和平台作一定的介绍,以便于后续的学习。
5.1 UNREFERENCED_PARAMETER Macro
主要为消除M$ C++编译器在Level 4 (/W4)产生的C4100 :unreferenced formal parameter警告,但在/TC编译器选项下不可用。
在M$ C++ Specification下还有其它的写法,但因其是M$ C++所特有,不便于学习标准C++,故在此不予考虑。
此种声明在C++中是合法的,主要为实现编译时多态(Compile-time Polymophism)。
可参看如下代码:
//CXxxx declarations
#pragma once
#include "stdafx.h"
template <typename T>
class CYyy {
public:
void Wellcome(const char* s)
{
T* pT = static_cast<T*>(this);
pT->sayHello(s);
}
void sayHello(const char* s)
{
std::cout<<"Hi, "<<s<<std::endl;
}
};
class CXxx1 : public CYyy<CXxx1> {
};
class CXxx2 : public CYyy<CXxx2> {
public:
void sayHello(const char* s)
{
std::cout<<"Wellcome, "<<s<<std::endl;
}
};
// main entry point
int _tmain(int argc, _TCHAR* argv[])
{
CXxx1 x1;
CXxx2 x2;
x1.Wellcome("Joe");
x2.Wellcome("JoeM");
return 0;
}
以上代码中Cyyy ::Wellcome相当于一个多态函数的一个包装器(wrapper),而所有的秘密就在T* pT = static_cast<T*>(this) 语句;编译时多态在空间开销上至少比运行时多态节省一个vtable指针的空间开销,且因其在编译时就已决定了函数入口点,故具有更高的执行效率。
C++ 异常规范(Exception Specifications),用于函数声明之后,通知编译器此函数不会抛出异常;但在ATL中主要目的是使M$ C 的SEH (Structured Exception Handling)在ATL中具有可移植性。
在ATL中经常可以看到这样的代码,如在atlwin.h中Cwindow的声明中:
class CWindow
{
public:
static RECT rcDefault;
HWND m_hWnd;
CWindow(HWND hWnd = NULL) throw() :
m_hWnd(hWnd)
{
}
...
}
查看本文来源