Learning WTL8.0

学习WTL可以有多种方式,当然如果有COM和ATL的知识背景最好不过,如果你有MFC编程背景却最为糟糕,除非你对MFC无所不知、无所不能(如果你不是MFC的ORACLE,那么最好忘却它)

 

 

Learning WTL8.0 Part-1 
Learning WTL8.0 Part-1
 
Win32 vs. ATL
Windows Programming

目录
目录.. 2
概要.. 3
1. “Hello World!” in Win32. 3
1.1创建一个Win32 Project3
1.1.1 选择Win32 Project3
1.1.2 应用程序设置.. 4
1.1.3 编译器选项设置.. 5
1.2 Win32程序的基本结构.. 6
1.2.1 WinMain. 6
1.2.1 Register windows class. 7
1.2.3 Initialize application. 7
1.2.4 WndProc8
1.3 Build与程序输出.. 9
2. “Hello World!” in ATL. 9
2.1 修改stdafx.h. 9
2.2 添加CWellcomeWindow.h. 10
2.3 修改win32.cpp. 10
2.4 Build与程序输出.. 11
3. ATL vs. Win32. 12
3.1 编程模型.. 12
3.1.1 Win32基础构造块和主要流程.. 13
3.1.2 ATL基础构造块和主要流程.. 13
3.2 Build过程和程序输出.. 13
4. ATL Windows编程模型.. 14
4.1 RegisterClass在哪?. 14
4.1.1 DECLARE_WND_CLASS(NULL)宏定义.. 14
4.1.2 CWndClassInfo结构(_ATL_WNDCLASSINFOW)定义.. 14
4.1.3 ATL::CWindowImpl::Create定义.. 15
4.2 WndProc在哪?.. 15
4.2.1 Message-Map. 15
5. Tips. 16
5.1 UNREFERENCED_PARAMETER Macro. 16
5.2 CXxx : public CYyy. 17
5.3 throw()18
总结.. 18
 

概要
学习WTL可以有多种方式,当然如果有COM和ATL的知识背景最好不过,如果你有MFC编程背景却最为糟糕,除非你对MFC无所不知、无所不能: -)(如果你不是MFC的ORACLE,那么最好忘却它)
 
本系列打算从Win32和ATL入手,来学习WTL,情况理想的话的可以做到一举四得: Win32、ATL、WTL、C++(OO和泛型编程)
 
WTL的文档相对较少,且有些文档多是针对WTL3.0和WTL7.1,相对于最新的WTL8.0有些人老珠黄,也些范式已不太常用了,这也是促使写本系列的最初想法,帮助自己也帮助和我有同样需要的人,如果可以的话。
 
本系列中所用的工具和库为VS.NET Team Edition 2005,WTL80-6137;如果你没有VS2005,可以到M$站点 http://msdn.microsoft.com/vstudio/express/ 上去下一个C++ Express;另外,假定你已安装好所有必备环境包括WTL了。
 
Part-1主要是从Win32和ATL着手,来讲解Windows编程的基本概念,有Win32和ATL基础的可以略过。: -)
 
建议:如果有条件的话最好跟随 1. “Hello World!” in Win32 和 2. “Hello World!” in ATL ,做完2个显示”Hello World!”的小程序,稍后将在 3. ATL vs. Win32 作进一步的讲解。
 
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点需要注意的,请按图示:
 
1.1.1 选择Win32 Project
Figure-1.1-1
Learning WTL8.0_第1张图片
 
1.1.2 应用程序设置
 
Figure-1.1-2
Learning WTL8.0_第2张图片
采用默认设置。
1.1.3 编译器选项设置
 
Figure-1.1-3
Learning WTL8.0_第3张图片
将C/C++ -> Advanced -> Compile As 设为 Compile as C Code(/TC), 这点很容易被忽视,如果你要写纯血统M$ C而又不想过多的关注C/C++文件扩展名的话;如果,你设置了 /TC编译器选项,请将  
UNREFERENCED_PARAMETER(hPrevInstance);
      UNREFERENCED_PARAMETER(lpCmdLine);
2 句代码注释(commenting out) 掉,关于 UNREFERENCED_PARAMETER 的使用将在 5.1 UNREFERENCED_PARAMETER Macro 中谈到。
 
 
1.2 Win32程序的基本结构
VS2005向导产生的Win32 Project的块结构(block)和VS2003已有较大的改进了: -)。
 
以下代码均为了更清楚地说明主要问题而剔除了无关紧要的部分。
1.2.1 WinMain
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;
}
 
1.2.4 WndProc
在这里,主要为处理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与程序输出
Learning WTL8.0_第4张图片
2. “Hello World!” in ATL
为了避免创建ATL COM EXE SERVER的开销和更好地说明Win32与ATL的内在关系,本例程序基于 1.1 创建一个Win32 Project的程序创建。 
 
对于ATL Windows编程陌生的,于此处不必过于在意,稍后在 3. 对Win32和ATL的初步观察和比较会做一定的讲解。
 
2.1 修改stdafx.h
在 1.1创建的Win32代码中移除或注释掉stdafx.h中的:
// Windows Header Files:
#include
 
// C RunTime Header Files
#include
#include
#include
#include
 
并在 stdafx.h 中添加如下的 include 语句:
 
#include
extern CComModule _Module;
#include
#include
#include "CWellcomeWindow.h"
 
2.2 添加CWellcomeWindow.h
#pragma once
#include "stdafx.h"
 
class CWellcomeWindow : public CWindowImpl
      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;
      }
};
2.3 修改win32.cpp
保留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;
}
 
2.4 Build与程序输出
如果在 1.1 创建一个Win32 Project设置了编译器选项的,先将编译器选项设置恢复为默认值即 /TP
 
程序输出如下:
Learning WTL8.0_第5张图片
 
以上的程序输出和 1.3 Build 与程序输出对比,似乎缺点什么?!在 Part-2再作交待。
3. ATL vs. Win32
首先,对Win32编程模型作了一个初步介绍,着重介绍ATL编程模型,这里并不存在对二者有孰优孰劣的假设;由于,对于作为M$ Windows程序员来说,或多或少、或直接或间接得都接触过Win32编程模型,故在本节中轻彼而重此。
 
其次,对Win32程序和ATL程序的Build过程和产生的可执行文件作一个初步的对照。
3.1 编程模型
Win32编程是整个Windows编程的基石,无论是ATL或WTL;因此,即使是ATL也必然包含Win32编程模型的基础构造块,只是ATL提供了轻量的基础构造块的封装(Encapsulation)。
 
Win32编程模型的基础构造块主要由Register windows class、Create window/Show window、Message loop和WndProc 4部分构成。
 
3.1.1 Win32基础构造块和主要流程
Learning WTL8.0_第6张图片
 
3.1.2 ATL基础构造块和主要流程
Learning WTL8.0_第7张图片
 
 
  • CComModule::Init & CComModule::Term : 是ATL关于COM Server的部分,在这里不必关注,在后续的WTL学习中还会相应地涉及到;
 
  • Main message loop : 在ATL中保留;
3.2 Build过程和程序输出
对Win32版与ATL版的Debug和Release分别作一个初步的对比;为说明一般性问题,在此不考虑编译器优化,因此,无论是Debug抑或Release均采用编译器默认设置。
 
 
Debug
Release
比重
Win32版
100K
68K
 
ATL版
244K
88K
D244% | R129%
 
ATL版的Release,无论是物理文件大小还是运行时空间大小或运行效率都与Win32版的Release接近,且二者除系统库外,基本上不需要其它支持库(可能msvcrt.dll,gdi32.dll等)。
 
4. ATL Windows编程模型
参照 3.1.1 Win32 基础构造块和主要流程,来初步的了解ATL Windows编程模型(有关ATL Windows编程的文档极为“罕见“, 甚至与WTL相比 : -)) ;ATL Windows编程是WTL的基石,当然有必要在此费一番力气。  
4.1 RegisterClass在哪?
在 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中)。
 
4.1.1 DECLARE_WND_CLASS(NULL)宏定义 
/
// 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; /
}
CWndClassInfo为ATL内使用WNDCLASSEX结构的结构(C++),看看代码就应该有所了解了,关键在于 4.1.2 CWndClassInfo 结构(_ATL_WNDCLASSINFOW)定义 的黑体部分( Bold)。 
 
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);
      }
};
 
4.1.3 ATL::CWindowImpl::Create定义
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。
4.2 WndProc在哪?
Main message loop在ATL予以了保留(例如,在win32.cpp中)。
 
WndProc由宏定义成员函数完成,参看 2.2 添加CWellcomeWindow.h可以看到如下代码:
      BEGIN_MSG_MAP(CWellcomeWindow)
            MESSAGE_HANDLER(WM_CREATE, OnCreate)
            MESSAGE_HANDLER(WM_PAINT, OnPaint)
            MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
      END_MSG_MAP()
 
4.2.1 Message-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; /
      }
 
你所定义的特定的消息处理将会置于 case 0defaul之间,例如 2.2 添加CWellcomeWindow.h将会产生如下代码:
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. Tips
对于一些技术点和平台作一定的介绍,以便于后续的学习。
5.1 UNREFERENCED_PARAMETER Macro
主要为消除M$ C++编译器在 Level 4 (/W4)产生的C4100 :unreferenced formal parameter警告,但在 /TC编译器选项下不可用。
 
消除C4100警告的还有另外一种常见的Unamed object的写法,如在 2.2 添加CWellcomeWindow.hOnPaint(UINT /*uMsg*/...) ;这两种为消除C4100警告的写法,并无明显的优劣之分,且都是平台可移植的;但,从标准C++的编码规范来说,笔者更倾向于后者。
在M$ C++ Specification下还有其它的写法,但因其是M$ C++所特有,不便于学习标准C++,故在此不予考虑。
5.2 CXxx : public CYyy
此种声明在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(this);
            pT->sayHello(s);
      }
 
      void sayHello(const char* s)
      {
            std::cout<<"Hi, "<
      }
};
 
class CXxx1 : public CYyy {
 
};
 
class CXxx2 : public CYyy {
public :
      void sayHello(const char* s)
      {
            std::cout<<"Wellcome, "<
      }
};
 
// 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(this) 语句;编译时多态在空间开销上至少比运行时多态节省一个vtable指针的空间开销,且因其在编译时就已决定了函数入口点,故具有更高的执行效率。
5.3 throw()
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)
      {
      }
...
}
 
 
总结
 
 
Win32 vs. ATL
Windows Programming


目录
目录.. 2
概要.. 3
1. “Hello World!” in Win32. 3
1.1创建一个Win32 Project3
1.1.1 选择Win32 Project3
1.1.2 应用程序设置.. 4
1.1.3 编译器选项设置.. 5
1.2 Win32程序的基本结构.. 6
1.2.1 WinMain. 6
1.2.1 Register windows class. 7
1.2.3 Initialize application. 7
1.2.4 WndProc8
1.3 Build与程序输出.. 9
2. “Hello World!” in ATL. 9
2.1 修改stdafx.h. 9
2.2 添加CWellcomeWindow.h. 10
2.3 修改win32.cpp. 10
2.4 Build与程序输出.. 11
3. ATL vs. Win32. 12
3.1 编程模型.. 12
3.1.1 Win32基础构造块和主要流程.. 13
3.1.2 ATL基础构造块和主要流程.. 13
3.2 Build过程和程序输出.. 13
4. ATL Windows编程模型.. 14
4.1 RegisterClass在哪?. 14
4.1.1 DECLARE_WND_CLASS(NULL)宏定义.. 14
4.1.2 CWndClassInfo结构(_ATL_WNDCLASSINFOW)定义.. 14
4.1.3 ATL::CWindowImpl::Create定义.. 15
4.2 WndProc在哪?.. 15
4.2.1 Message-Map. 15
5. Tips. 16
5.1 UNREFERENCED_PARAMETER Macro. 16
5.2 CXxx : public CYyy. 17
5.3 throw()18
总结.. 18
 


概要
学习WTL可以有多种方式,当然如果有COM和ATL的知识背景最好不过,如果你有MFC编程背景却最为糟糕,除非你对MFC无所不知、无所不能: -)(如果你不是MFC的ORACLE,那么最好忘却它)
 
本系列打算从Win32和ATL入手,来学习WTL,情况理想的话的可以做到一举四得: Win32、ATL、WTL、C++(OO和泛型编程)
 
WTL的文档相对较少,且有些文档多是针对WTL3.0和WTL7.1,相对于最新的WTL8.0有些人老珠黄,也些范式已不太常用了,这也是促使写本系列的最初想法,帮助自己也帮助和我有同样需要的人,如果可以的话。
 
本系列中所用的工具和库为VS.NET Team Edition 2005,WTL80-6137;如果你没有VS2005,可以到M$站点 http://msdn.microsoft.com/vstudio/express/ 上去下一个C++ Express;另外,假定你已安装好所有必备环境包括WTL了。
 
Part-1主要是从Win32和ATL着手,来讲解Windows编程的基本概念,有Win32和ATL基础的可以略过。: -)
 
建议:如果有条件的话最好跟随 1. “Hello World!” in Win32 和 2. “Hello World!” in ATL ,做完2个显示”Hello World!”的小程序,稍后将在 3. ATL vs. Win32 作进一步的讲解。
 
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点需要注意的,请按图示:
 
1.1.1 选择Win32 Project
Figure-1.1-1
Learning WTL8.0_第8张图片
 
1.1.2 应用程序设置
 
Figure-1.1-2
Learning WTL8.0_第9张图片
采用默认设置。
1.1.3 编译器选项设置
 
Figure-1.1-3
Learning WTL8.0_第10张图片
将C/C++ -> Advanced -> Compile As 设为 Compile as C Code(/TC), 这点很容易被忽视,如果你要写纯血统M$ C而又不想过多的关注C/C++文件扩展名的话;如果,你设置了 /TC编译器选项,请将  
UNREFERENCED_PARAMETER(hPrevInstance);
      UNREFERENCED_PARAMETER(lpCmdLine);
2 句代码注释(commenting out) 掉,关于 UNREFERENCED_PARAMETER 的使用将在 5.1 UNREFERENCED_PARAMETER Macro 中谈到。
 
 
1.2 Win32程序的基本结构
VS2005向导产生的Win32 Project的块结构(block)和VS2003已有较大的改进了: -)。
 
以下代码均为了更清楚地说明主要问题而剔除了无关紧要的部分。
1.2.1 WinMain
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;
}
 
1.2.4 WndProc
在这里,主要为处理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与程序输出
Learning WTL8.0_第11张图片
2. “Hello World!” in ATL
为了避免创建ATL COM EXE SERVER的开销和更好地说明Win32与ATL的内在关系,本例程序基于 1.1 创建一个Win32 Project的程序创建。 
 
对于ATL Windows编程陌生的,于此处不必过于在意,稍后在 3. 对Win32和ATL的初步观察和比较会做一定的讲解。
 
2.1 修改stdafx.h
在 1.1创建的Win32代码中移除或注释掉stdafx.h中的:
// Windows Header Files:
#include
 
// C RunTime Header Files
#include
#include
#include
#include
 
并在 stdafx.h 中添加如下的 include 语句:
 
#include
extern CComModule _Module;
#include
#include
#include "CWellcomeWindow.h"
 
2.2 添加CWellcomeWindow.h
#pragma once
#include "stdafx.h"
 
class CWellcomeWindow : public CWindowImpl
      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;
            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;
      }
};
2.3 修改win32.cpp
保留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;
}
 
2.4 Build与程序输出
如果在 1.1 创建一个Win32 Project设置了编译器选项的,先将编译器选项设置恢复为默认值即 /TP
 
程序输出如下:
Learning WTL8.0_第12张图片
 
以上的程序输出和 1.3 Build 与程序输出对比,似乎缺点什么?!在 Part-2再作交待。
3. ATL vs. Win32
首先,对Win32编程模型作了一个初步介绍,着重介绍ATL编程模型,这里并不存在对二者有孰优孰劣的假设;由于,对于作为M$ Windows程序员来说,或多或少、或直接或间接得都接触过Win32编程模型,故在本节中轻彼而重此。
 
其次,对Win32程序和ATL程序的Build过程和产生的可执行文件作一个初步的对照。
3.1 编程模型
Win32编程是整个Windows编程的基石,无论是ATL或WTL;因此,即使是ATL也必然包含Win32编程模型的基础构造块,只是ATL提供了轻量的基础构造块的封装(Encapsulation)。
 
Win32编程模型的基础构造块主要由Register windows class、Create window/Show window、Message loop和WndProc 4部分构成。
 
3.1.1 Win32基础构造块和主要流程
Learning WTL8.0_第13张图片
 
3.1.2 ATL基础构造块和主要流程
Learning WTL8.0_第14张图片
 
 
  • CComModule::Init & CComModule::Term : 是ATL关于COM Server的部分,在这里不必关注,在后续的WTL学习中还会相应地涉及到;
 
  • Main message loop : 在ATL中保留;
3.2 Build过程和程序输出
对Win32版与ATL版的Debug和Release分别作一个初步的对比;为说明一般性问题,在此不考虑编译器优化,因此,无论是Debug抑或Release均采用编译器默认设置。
 

 
Debug
Release
比重
Win32版
100K
68K
 
ATL版
244K
88K
D244% | R129%

 
ATL版的Release,无论是物理文件大小还是运行时空间大小或运行效率都与Win32版的Release接近,且二者除系统库外,基本上不需要其它支持库(可能msvcrt.dll,gdi32.dll等)。
 
4. ATL Windows编程模型
参照 3.1.1 Win32 基础构造块和主要流程,来初步的了解ATL Windows编程模型(有关ATL Windows编程的文档极为“罕见“, 甚至与WTL相比 : -)) ;ATL Windows编程是WTL的基石,当然有必要在此费一番力气。
4.1 RegisterClass在哪?
在 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中)。
 
4.1.1 DECLARE_WND_CLASS(NULL)宏定义 
/
// 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; /
}
CWndClassInfo为ATL内使用WNDCLASSEX结构的结构(C++),看看代码就应该有所了解了,关键在于 4.1.2 CWndClassInfo 结构(_ATL_WNDCLASSINFOW)定义的黑体部分( Bold)。 
 
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);
      }
};
 
4.1.3 ATL::CWindowImpl::Create定义
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。
4.2 WndProc在哪?
Main message loop在ATL予以了保留(例如,在win32.cpp中)。
 
WndProc由宏定义成员函数完成,参看 2.2 添加CWellcomeWindow.h可以看到如下代码:
      BEGIN_MSG_MAP(CWellcomeWindow)
            MESSAGE_HANDLER(WM_CREATE, OnCreate)
            MESSAGE_HANDLER(WM_PAINT, OnPaint)
            MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
      END_MSG_MAP()
 
4.2.1 Message-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; /
      }
 
你所定义的特定的消息处理将会置于 case 0defaul之间,例如 2.2 添加CWellcomeWindow.h将会产生如下代码:
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. Tips
对于一些技术点和平台作一定的介绍,以便于后续的学习。
5.1 UNREFERENCED_PARAMETER Macro
主要为消除M$ C++编译器在 Level 4 (/W4)产生的C4100 :unreferenced formal parameter警告,但在 /TC编译器选项下不可用。
 
消除C4100警告的还有另外一种常见的Unamed object的写法,如在 2.2 添加CWellcomeWindow.hOnPaint(UINT /*uMsg*/...) ;这两种为消除C4100警告的写法,并无明显的优劣之分,且都是平台可移植的;但,从标准C++的编码规范来说,笔者更倾向于后者。
在M$ C++ Specification下还有其它的写法,但因其是M$ C++所特有,不便于学习标准C++,故在此不予考虑。
5.2 CXxx : public CYyy
此种声明在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(this);
            pT->sayHello(s);
      }
 
      void sayHello(const char* s)
      {
            std::cout<<"Hi, "<
      }
};
 
class CXxx1 : public CYyy {
 
};
 
class CXxx2 : public CYyy {
public :
      void sayHello(const char* s)
      {
            std::cout<<"Wellcome, "<
      }
};
 
// 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(this) 语句;编译时多态在空间开销上至少比运行时多态节省一个vtable指针的空间开销,且因其在编译时就已决定了函数入口点,故具有更高的执行效率。
5.3 throw()
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)
      {
      }
...
}
 
 
总结
 

你可能感兴趣的:(COM&&ATL)