天天看點

深入剖析WTL——Win32模型

WTL 是Windows Template Library的縮寫。最初,WTL是由微軟的ATL(Active Template Library)小組成員開發的一個SDK例子。主要是基于ATL的對Win32 API的封裝。從2.0後,功能逐漸完善,成為了一個完整的支援視窗的架構(windows framework)。

與MFC相比較,功能并沒有MFC完善。比如MFC支援doc/view架構,而WTL并不支援。同時,WTL也沒有Microsoft的官方支援。但是,WTL是基于模版(template)的,其應用程式最小隻有24KB,同時不象MFC,依賴DLL(MFC需要MFC42.DLL)。

WTL系列文章對WTL進行了深入剖析,希望能友善您對WTL有一個深入的了解,進而能得心應手的開發出高品質的Windows應用程式。

Win32的線程模型

為了便于以後的探讨,首先看一下Win32的線程模型。

一個Win32應用程式(或程序)是由一個或多個并發的線程組成的,其中第一個啟動的線程稱為主線程。

Win32定義了兩種類型的線程,界面線程和工作線程。Win32的每個程序可以有一個或多個界面線程和/或多個工作線程。界面線程擁有一個或多個視窗,擁有一個消息隊列和其它屬于界面線程的元素。工作線程就是一般的線程,它沒有視窗,沒有消息隊列。

界面線程通常有一個或幾個視窗。當某一個視窗有消息時,界面線程會調用相應的視窗函數(Windows Process)來處理該事件。由于某消息循環由它界面線程處理,同時不必在乎是哪個線程發送消息的,是以,Windows會保證線程間的同步問題。

對于工作線程,線程間的同步必須由程式員來實作。盡可能避免死鎖和競争出現。

Win32應用程式模型

Win32應用程式可以分成兩大類:控制台程式(console application)和視窗界面程式(windows GUI application)。控制台程式的入口函數是main(),視窗界面程式的入口函數是WinMain()。

入口函數就是程式的主線程的運作起點。

這裡讨論的開發架構(Framework)是針對視窗界面程式的。

視窗界面程式通常分成以下幾類:SDI, MDI, multi-SDI, 和Dialog應用程式。

SDI(Single Document Interface)應用程式通常隻有一個主視窗(通常是一個架構視窗,Frame Window)。架構視窗包含菜單、工具欄、狀态欄和稱為視(View)的客戶工作區。

multi-SDI(Multiple Threads SDI)應用程式有架構個主視窗。比如IE浏覽器,使用"檔案/建立/視窗"指令後,會出現另一個IE視窗。

MDI(Multiple Document Interface)應用程式有一個主架構視窗,但有多個子架構視窗。每個子視窗都有自己的視(View)。

Dialog應用程式是基于對話框的。

通常一個簡單的SDI應用程式由兩個函數組成。一個是應用程式入口函數WinMain(),另一個是該應用程式視窗的視窗函數。

程式(主線程)從入口函數開始運作。在該函數中,首先是注冊并建立一個主視窗。然後,啟動消息循環。消息循環中,根據不同的消息,将消息發送到視窗函數中處理。當消息是退出消息時,該入口函數會退出消息循環,然後結束程式。

下面是一個最簡單的Windows界面應用程式。

//應用程式入口函數
int APIENTRY WinMain(HINSTANCE hInstance,
      HINSTANCE hPrevInstance,
      LPSTR lpCmdLine,
      int nCmdShow)
{
   MSG msg;

   //1. 注冊視窗類
   WNDCLASSEX wcex;
   wcex.cbSize = sizeof(WNDCLASSEX); 
   wcex.style = CS_HREDRAW | CS_VREDRAW;
   wcex.lpfnWndProc = (WNDPROC)WndProc;    //指定視窗函數
   wcex.cbClsExtra = 0;
   wcex.cbWndExtra = 0;
   wcex.hInstance = hInstance;
   wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_HELLOWORLD);
   wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
   wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
   wcex.lpszMenuName = (LPCSTR)IDC_HELLOWORLD;
   wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance,(LPCTSTR)IDI_SMALL);
   RegisterClassEx(&wcex);
   //2. 建立一個該類型的視窗
   HWND hWnd;
   hWnd = CreateWindow(szWindowClass,szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 
CW_USEDEFAULT, 0, NULL, NULL, 
hInstance, NULL);
   if (!hWnd) return FALSE;
   //3. 按nCmdShow所指定的方式顯示視窗
   ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
   //4. 啟動消息循環,将消息發送給相應的視窗函數
   while (GetMessage(&msg, NULL, 0, 0)) 
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return msg.wParam;
}
//視窗函數
LRESULT CALLBACK WndProc(HWND hWnd, 
UINT message, 
WPARAM wParam,         
LPARAM lParam)
{
   PAINTSTRUCT ps;
   HDC hdc;
   char* szHello = "Hello, world!";
   switch (message) 
   {
     case WM_PAINT:
      hdc = BeginPaint(hWnd, &ps);
      RECT rt;
      GetClientRect(hWnd, &rt);
      DrawText(hdc, szHello, strlen(szHello),&rt,DT_CENTER);
      EndPaint(hWnd, &ps);
      break;
     case WM_DESTROY:
      PostQuitMessage(0);  //退出應用程式
      break;
     default:
      return DefWindowProc(hWnd, message, wParam, lParam);
     }
    return 0;
  }
              

上面程式的執行過程如下:

1、注冊視窗類

在使用CreateWindwo()或CreateWindowEx()建立視窗時,必須提供一個辨別視窗類的字元串。該視窗類定義了一些視窗的基本屬性。其中一個重要的工作是向作業系統提供視窗函數。該函數是回調函數,用于處理發送給該視窗的消息。

在上面程式中,僅僅簡單的處理了兩個消息。一個是向視窗區域畫出"Hello world."字元串。另一個是當視窗撤消時,向應用程式發送"退出應用程式"消息。

2、建立視窗 3、顯示視窗 4、啟動消息循環,分發并處理消息。
     while (GetMessage(&msg, NULL, 0, 0)) 
     {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
     }
              

在上述消息循環代碼中,調用GetMessage()從線程的消息隊列中取出一條消息。如果消息為0(由視窗函數處理"WM_DESTROY"消息時發送的"PostQuitMessage(0)"),會退出消息循環。

然後調用TranslateMessage()翻譯消息。

翻譯後,再調用DispatchMessage()将該消息分發至相應的視窗函數進行處理。 (實際上DispatchMessage()将該消息作為參數調用對應的視窗的視窗函數,這就是分發的實質)。

繼續閱讀