成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

網絡安全編程:Windows消息機制實例

安全
那么窗口接收到消息后的一系列行為是如何發生的?下面通過熟悉Windows的消息機制來理解消息處理背后的秘密。

[[376643]]

 SendMessage()將指定的消息發送給指定的窗口,窗口接收到消息也有相應的行為發生。那么窗口接收到消息后的一系列行為是如何發生的?下面通過熟悉Windows的消息機制來理解消息處理背后的秘密。

01 DOS程序與Windows程序執行流程對比

Windows下的窗口應用程序都是基于消息機制的,操作系統與應用程序之間、應用程序與應用程序之間,大部分都是通過消息機制進行通信、交互的。要真正掌握Windows應用程序內部對消息的處理,必須分析實際的源代碼。在編寫一個基于消息的Windows應用程序前,先來比較DOS程序和Windows程序在執行時的流程。

1. DOS程序執行流程

在DOS下將編寫完的程序進行執行,在執行時有較為清晰的流程。比如用C語言編寫程序后,程序執行時的大致流程如圖1所示。

圖1  傳統DOS程序執行流程

在圖1中可以看出,DOS程序的流程是按照代碼的順序(這里的順序并不是指程序控制結構中的順序、分支和循環的意思,而是指程序運行的邏輯有明顯的流程)和流程依次執行。大致步驟為:DOS程序從main()主函數開始執行(其實程序真正的入口并不是main()函數);執行的過程中按照代碼編寫流程依次調用各個子程序;在執行的過程中會等待用戶的輸入等操作;當各個子程序執行完成后,最終會返回main()主函數,執行main()主函數的return語句后,程序退出(其實程序真正的出口也并不是main()函數的return語句)。

2. Windows程序執行流程

DOS程序的執行流程比較簡單,但是Windows應用程序的執行流程就比較復雜了。DOS是單任務的操作系統。在DOS中,通過輸入命令,DOS操作系統會將控制權由Command.com轉交給DOS程序從而執行。而Windows是多任務的操作系統,在Windows下同時會運行若干個應用程序,那么Windows就無法把控制權完全交給一個應用程序。Windows下的應用程序是如何工作的?首先看一下Windows應用程序內部的大致結構圖,如圖2所示。

圖2  Windows應用程序執行原理圖

圖2可能看起來比較復雜,其實Windows應用程序的內部結構比該示意圖更復雜。在實際開發Windows應用程序時,需要關注的部分主要是“主程序”和“窗口過程”兩部分。但是從圖2來看,主程序和窗口過程沒有直接的調用關系,而在主程序和窗口過程之間有一個“系統程序模塊”。“主程序”的功能是用來注冊窗口類、獲取消息和分發消息。而“窗口過程”中定義了需要處理的消息,“窗口過程”會根據不同的消息執行不同的動作,而不需要程序處理的消息則會交給默認的系統過程進行處理。

在“主程序”中,RegisterClassEx()函數會注冊一個窗口類,窗口類中的字段中包含了“窗口過程”的地址信息,也就是把“窗口類”的信息(包括“窗口過程的地址信息”)告訴操作系統。然后“主程序”不斷通過調用GetMessage()函數獲取消息,再交由DispatchMessge()函數來分發消息。消息分發后并沒有直接調用“窗口過程”讓其處理消息,而是由系統模塊查找該窗口指定的窗口類,通過窗口類再找到窗口過程的地址,最后將消息送給該窗口過程,由窗口過程處理消息。

02 一個簡單的Windows應用程序

相對一個簡單的DOS程序來說一個簡單的Windows應用程序要很長。下面的例子中只實現了一個特別簡單的Windows程序,這個程序在桌面上顯示一個簡單的窗口,它沒有菜單欄、工具欄、狀態欄,只是在窗口中輸出一段簡單的字符串。雖然程序如此簡單,但是也要編寫100行左右的代碼。考慮到初學的朋友,這里將一部分一部分地逐步介紹代碼中的細節,以減少代碼的長度,從而方便初學者的學習。

1. Windows窗口應用程序的主函數——WinMain()

在DOS時代,或編寫Windows下的命令行的程序,要使用C語言編寫代碼的時候都是從main()函數開始的。而在Windows下編寫有窗口的程序時,要用C語言編寫窗口程序就不再從main()函數開始了,取而代之的是WinMain()函數。

既然Windows應用程序的主函數是WinMain(),那么就從了解WinMain()函數的定義開始學習Windows應用程序的開發。WinMain()函數的定義如下: 

  1. int WINAPI WinMain(  
  2.  HINSTANCE hInstance,  
  3.  HINSTANCE hPrevInstance,  
  4.  LPSTR lpCmdLine,  
  5.  int nCmdShow  
  6. ); 

該函數的定義取自MSDN中,在看到WinMain()函數的定義后,很直觀地會發現WinMain函數的參數比main()函數的參數變多了。從參數個數上來說,WinMain()函數接收的信息更多了。下面來看每個參數的含義。

hInstance是應用程序的實例句柄。保存在磁盤上的程序文件是靜態的,當被加載到內存中時,被分配了CPU、內存等進程所需的資源后,一個靜態的程序就被實例化為一個有各種執行資源的進程了。句柄的概念隨上下文的不同而不同,句柄是操作某個資源的“把手”。當需要對某個實例化進程操作時,需要借助該實例句柄進行操作。這里的實例句柄是程序裝入內存后的起始地址。實例句柄的值也可以通過GetModuleHandle()參數來獲得(注意系統中沒有GetInstanceHandle()函數,不要誤以為是hInstance就會有GetInstance×××()類的函數)。

句柄這個詞在開發Windows程序時是非常常見的一個詞。“句柄”一詞的含義隨上下文的不同而所有改變。比如,磁盤上的程序文件被加載到內存中后,就創建了一個實例句柄,這個實例句柄是程序裝入內存后的“起始地址”,或者說是“模塊的起始地址”。

拿SendMessage()函數舉例來說,句柄相當于一個操作的面板,對句柄發送的消息相當于面板上的各個開關按鍵,消息的附加數據,相當于給開關按鍵送的各種參數,這些參數根據按鍵的不同而不同。

hPrevInstance是同一個文件創建的上一個實例的實例句柄。這個參數是Win16平臺下的遺留物,在Win32下已經不再使用了。

lpCmdLine是主函數的參數,用于在程序啟動時給進程傳遞參數。比如在“開始”菜單的“運行”中輸入“notepad c:\boot.ini”,這樣就通過記事本打開了C盤下的boot.ini文件。C:\Boot.ini文件是通過WinMain()函數的lpCmdLine參數傳遞給notepad.exe程序的。

nCmdShow是進程顯示的方式,可以是最大化顯示、最小化顯示,或者是隱藏等顯示方式(如果是啟動木馬程序的話,啟動方式當然要由自己進行控制)。

主函數的參數都介紹完了。編寫Windows的窗口程序,需要主函數中應該完成哪些操作是下面要討論的內容。

2. WinMain()函數中的流程

編寫Windows下的窗口程序,在WinMain()主函數中主要完成的任務是注冊一個窗口類,創建一個窗口并顯示創建的窗口,然后不停地獲取屬于自己的消息并分發給自己的窗口過程,直到收到WM_QUIT消息后退出消息循環結束進程。這是主函數中程序的執行脈絡,程序中將注冊窗口類、創建窗口的操作封裝為自定義函數。

代碼如下: 

  1. int WINAPI WinMain(  
  2.  HINSTANCE hInstance,  
  3.  HINSTANCE hPrevInstance,  
  4.  LPSTR lpCmdLine,  
  5.  int nCmdShow)  
  6.  
  7.   MSG Msg;  
  8.   BOOL bRet;  
  9.   // 注冊窗口類  
  10.   MyRegisterClass(hInstance);  
  11.   // 創建窗口并顯示窗口  
  12.   if ( !InitInstance(hInstance, SW_SHOWNORMAL) )  
  13.   {  
  14.     return FALSE; 
  15.   }  
  16.   // 消息循環  
  17.   // 獲取屬于自己的消息并進行分發  
  18.   while( (bRet = GetMessage(&Msg, NULL, 0, 0)) != 0 )  
  19.   {  
  20.     if ( bRet == -1 )  
  21.     {  
  22.       // handle the error and possibly exit  
  23.       break;  
  24.     }  
  25.     else  
  26.     {  
  27.       TranslateMessage(&Msg);  
  28.       DispatchMessage(&Msg);  
  29.     }  
  30.   }  
  31.   return Msg.wParam;  

在代碼中,MyRegisterClass()和InitInstance()是兩個自定義的函數,分別用來注冊窗口類,創建窗口并顯示更新創建的窗口。后面的消息循環部分用來獲得消息并進行消息分發。它的流程如圖2所示的“主程序”部分。

代碼中主要是3個函數,分別是GetMessage()、TranslateMessage()和DispatchMessage()。這3個函數是Windows提供的API函數。GetMessage()的定義如下: 

  1. BOOL GetMessage(  
  2.  LPMSG lpMsg,  
  3.  HWND hWnd,  
  4.  UINT wMsgFilterMin,  
  5.  UINT wMsgFilterMax  
  6. ); 

該函數用來獲取屬于自己的消息,并填充MSG結構體。有一個類似于GetMessage()的函數是PeekMessage(),它可以判斷消息隊列中是否有消息,如果沒有消息,可以主動讓出CPU時間給其他進程。關于PeekMessage()函數的使用,請參考MSDN: 

  1. BOOL TranslateMessage(CONST MSG *lpMsg); 

該函數是用來處理鍵盤消息的。它將虛擬碼消息轉換為字符消息,也就是將WM_KEYDOWN消息和WM_KEYUP消息轉換為WM_CHAR消息,將WM_SYSKEYDOWN消息和WM_SYSKEYUP消息轉換為WM_SYSCHAR消息: 

  1. LRESULT DispatchMessage(CONST MSG *lpmsg); 

該函數是將消息分發到窗口過程中。

3. 注冊窗口類的自定義函數

在WinMain()函數中,首先調用了MyRegisterClass()這個自定義函數,需要傳遞進程的實例句柄hInstance作為參數。該函數完成窗口類的注冊,分為兩步:第一步是填充WNDCLASSEX結構體,第二步是調用RegisterClassEx()函數進行注冊。該函數相對簡單,但是,該函數中稍微復雜的是WNDCLASSEX結構體的成員較多。

代碼如下: 

  1. ATOM MyRegisterClass(HINSTANCE hInstance)  
  2.  
  3.   WNDCLASSEX WndCls;  
  4.   // 填充結構體為 0  
  5.   ZeroMemory(&WndCls, sizeof(WNDCLASSEX));  
  6.   // cbSize 是結構體大小  
  7.   WndCls.cbSize = sizeof(WNDCLASSEX);  
  8.   // lpfnWndProc 是窗口過程地址  
  9.   WndCls.lpfnWndProc = WindowProc 
  10.   // hInstance 是實例句柄  
  11.   WndCls.hInstance = hInstance;  
  12.   // lpszClassName 是窗口類類名  
  13.   WndCls.lpszClassName = CLASSNAME 
  14.   // style 是窗口類風格  
  15.   WndCls.style = CS_HREDRAW | CS_VREDRAW;  
  16.   // hbrBackground 是窗口類背景色  
  17.   WndCls.hbrBackground = (HBRUSH)COLOR_WINDOWFRAME + 1;  
  18.   // hCursor 是鼠標句柄  
  19.   WndCls.hCursor = LoadCursor(NULL, IDC_ARROW);  
  20.   // hIcon 是圖標句柄  
  21.   WndCls.hIcon = LoadIcon(NULL, IDI_QUESTION);  
  22.   // 其他  
  23.   WndCls.cbClsExtra = 0 
  24.   WndCls.cbWndExtra = 0 
  25.   return RegisterClassEx(&WndCls);  

在代碼中,WNDCLASSEX結構體的成員都介紹了。WNDCLASSEX中最重要的字段是lpfnWndProc,它將保存的是窗口過程的地址。窗口過程是對各種消息進程處理的“匯集地”,也是編寫Windows應用程序的重點部分。代碼中的函數都比較簡單,主要涉及LoadCursor()、LoadIcon()和RegisterClassEx()這3個函數。由于這3個函數使用簡單,通過代碼就可以進行理解,這里不做過多介紹。

注冊窗口類(提到窗口類,你是否想到了FindWindow()函數的第一個參數呢?)的重點是在后面的代碼中可以根據該窗口類創建該種類型的窗口。代碼中,在定義窗口類時指定了背景色、鼠標指針、窗口圖標等,那么使用該窗口類創建的窗口都具有相同的窗口類型。

4. 創建主窗口并顯示更新

注冊窗口類后,根據該窗口類創建具體的主窗口并顯示和更新窗口。

代碼如下: 

  1. BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)  
  2.  
  3.   HWND hWnd = NULL 
  4.   // 創建窗口  
  5.   hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,  
  6.     CLASSNAME,  
  7.     "MyFirstWindow",  
  8.     WS_OVERLAPPEDWINDOW,  
  9.     CW_USEDEFAULT, CW_USEDEFAULT,  
  10.     CW_USEDEFAULT, CW_USEDEFAULT,  
  11.     NULL, NULL, hInstance, NULL);  
  12.   if ( NULL == hWnd )  
  13.   {  
  14.     return FALSE;  
  15.   }  
  16.   // 顯示窗口  
  17.   ShowWindow(hWnd, nCmdShow);  
  18.   // 更新窗口  
  19.   UpdateWindow(hWnd);  
  20.   return TRUE;  

在調用該函數時,需要給該函數傳遞實例句柄和窗口顯示方式兩個參數。這兩個參數的第1個參數通過WinMain()函數的參數hInstance指定,第2個參數可以通過WinMain()函數的第3個參數指定,也可以進行自定義指定。程序中的調用代碼如下: 

  1. InitInstance(hInstance, SW_SHOWNORMAL); 

在創建主窗口時調用了CreateWindowEx()函數,先來看看它的函數原型: 

  1. HWND CreateWindowEx(  
  2.  DWORD dwExStyle,  
  3.  LPCTSTR lpClassName,  
  4.  LPCTSTR lpWindowName,  
  5.  DWORD dwStyle,  
  6.  int x,  
  7.  int y,  
  8.  int nWidth,  
  9.  int nHeight,  
  10.  HWND hWndParent,  
  11.  HMENU hMenu,  
  12.  HINSTANCE hInstance,  
  13.  LPVOID lpParam  
  14. ); 

CreateWindowEx()中的第2個參數是lpClassName,由注釋可以知道是已經注冊的類名。這個已經注冊的類名就是WNDCLASSEX結構體的lpszClassName字段。

5. 處理消息的窗口過程

按照如圖2所示的流程,WinMain()主函數的部分已經都實現完成了。接下來看程序中關鍵的部分——窗口過程。從WinMain()主函數中看出,在WinMain()主函數中沒有任何地方直接調用窗口過程,只是在注冊窗口類時指定了窗口過程的地址。那么窗口類是由誰進行調用的呢?答案是由操作系統進行調用的。原因有二,首先窗口過程的地址是由系統維護的,注冊窗口類時是將“窗口過程的地址”向操作系統進行注冊。其次是除了應用程序本身會調用自己的窗口過程外,其他應用程序也會調用自己的窗口過程,比如前面的例子中調用SendMessage()函數發送消息后,需要系統調用目標程序的窗口過程來完成相應的動作。如果窗口過程由自己調用,那么窗口就要自己維護窗口類的信息,進程間消息的通信會非常繁瑣,也會無形中增加系統的開銷。

窗口過程的代碼如下: 

  1. LRESULT CALLBACK WindowProc(  
  2.     HWND hwnd,  
  3.     UINT uMsg,  
  4.     WPARAM wParam,  
  5.     LPARAM lParam)  
  6.  
  7.   PAINTSTRUCT ps;  
  8.   HDC hDC;  
  9.   RECT rt;  
  10.   char *pszDrawText = "Hello Windows Program." 
  11.   switch (uMsg)  
  12.   {  
  13.   case WM_PAINT:  
  14.     {  
  15.       hDC = BeginPaint(hwnd, &ps);  
  16.       GetClientRect(hwnd, &rt);  
  17.       DrawTextA(hDC,  
  18.         pszDrawText, strlen(pszDrawText),&rt,  
  19.         DT_CENTER | DT_VCENTER | DT_SINGLELINE);  
  20.       EndPaint(hwnd, &ps);  
  21.       break;  
  22.     }  
  23.   case WM_CLOSE:  
  24.     {  
  25.       if ( IDYES == MessageBox(hwnd,  
  26.         "是否退出程序", "MyFirstWin", MB_YESNO) )  
  27.       {  
  28.         DestroyWindow(hwnd);  
  29.         PostQuitMessage(0);  
  30.       }  
  31.       break;  
  32.     }  
  33.   default:  
  34.     {  
  35.       return DefWindowProc(hwnd, uMsg, wParam, lParam);  
  36.     }  
  37.   }  
  38.   return 0;  

在WinMain()函數中,通過調用RegisterClassEx()函數進行了窗口類的注冊,通過調用CreateWindowEx()函數創建了窗口,并且GetMessage()函數不停地獲取消息,但是在主函數中沒有對被創建的窗口做任何處理。那是因為真正對窗口行為的處理全部放在了窗口過程中。當WinMain()函數中的消息循環得到消息以后,通過調用DispatchMessage()函數將消息派發(實際不是由DispatchMessage()函數直接派發)給了窗口過程,從而由窗口過程對消息進行處理。

窗口過程的定義是按照MSDN上給出的形式進行定義的,MSDN上的定義形式如下: 

  1. LRESULT CALLBACK WindowProc(  
  2.  HWND hwnd,  
  3.  UINT uMsg,  
  4.  WPARAM wParam,  
  5.  LPARAM lParam  
  6. ); 

WindowProc是窗口過程的函數名,這個函數名可以隨意改變,但是該窗口過程的函數名必須與WNDCLASSEX結構體中lpfnWndProc的成員變量的值一致。函數的第1個參數hwnd是窗口的句柄,第2個參數uMsg是消息值,第3個和第4個參數是對于消息值的附加參數。這4個參數的類型與SendMessage()函數的參數相對應。

上面WindowProc()窗口過程中只對兩個消息進行了處理,分別是WM_PAINT和WM_CLOSE。這里為了演示因此只簡單處理了兩個消息。Windows中有上千種消息,那么多的消息不可能全部都由程序員自己去處理,程序員只處理一些程序中需要的消息,其余的消息就交給了DefWindowProc()函數進行處理。DefWindowProc()函數實際上是將消息傳遞給了操作系統,由操作系統來處理程序中沒有處理的消息。比如,在調用CreateWindow()函數時,系統會發送消息WM_CREATE給窗口過程,但是這個消息可能對程序的功能并不需要進行特殊的處理,因此直接交由DefWindowProc()函數讓系統進行處理。

DefWindowProc()函數的定義如下: 

  1. LRESULT DefWindowProc(  
  2.  HWND hWnd,  
  3.  UINT Msg,  
  4.  WPARAM wParam,  
  5.  LPARAM lParam  
  6. ); 

該函數的4個參數跟窗口過程的參數相同,只要將窗口過程的參數依次傳遞給DefWindowProc()函數就可以完成該函數的調用。在switch分支結構中的default位置直接調用DefWindowProc()函數就可以了。

WM_CLOSE消息是關閉窗口時發出的消息,在這個消息中需要調用DestoryWindow()函數來銷毀窗口,并且調用PostQuitMessage()來退出消息循環,使程序退出。對于WM_PAINT消息,這里不進行介紹,涉及的幾個API函數可以參考MSDN進行了解。

有的資料在介紹消息循環時會給出一個建議,就是把需要經常處理的消息放到程序靠上的位置,而將不經常處理的消息放到程序靠下的位置,從而提高程序的效率。其實,在窗口過程中往往會使用switch結構對消息進行判斷(如果使用if和else結構進行消息的判斷,那么常用的消息是要放到前面),而switch結構在編譯器進行編譯后會進行優化處理,從而大大提高程序的運行效率。 

 

責任編輯:龐桂玉 來源: 計算機與網絡安全
相關推薦

2021-05-24 11:55:55

網絡安全Windows鉤子函數

2021-04-26 10:32:38

網絡安全PE編程工具

2021-04-30 18:50:44

網絡安全PE編程添加節區

2021-03-03 12:20:42

網絡安全DLL編程

2021-02-19 09:30:52

網絡安全服務控制管理器代碼

2021-04-28 14:35:48

網絡安全PE編程代碼

2021-04-25 21:25:09

網絡安全網絡安全編程PE編程

2021-03-05 13:46:56

網絡安全遠程線程

2021-01-26 13:45:03

網絡安全Winsock編程

2021-01-22 10:58:16

網絡安全進程間碼如

2021-02-21 18:19:43

網絡安全網絡安全編程創建進程

2021-02-23 10:20:07

網絡安全進程代碼

2011-05-20 14:23:59

WLANWEPWPA

2016-10-10 00:18:27

2021-06-18 09:55:09

網絡安全目錄監控

2010-09-26 08:46:08

802.1x

2011-03-17 13:32:45

2018-08-06 15:33:02

網絡安全CISO投資

2021-03-15 13:50:24

網絡安全Android安全機制

2021-03-01 11:20:13

網絡安全多線程代碼
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 涩爱av一区二区三区 | 国产一区二区久久久 | 青青草视频网 | 久久精品免费一区二区三 | 91精品国产高清一区二区三区 | 久久久久久国产精品三区 | 久久久视频在线 | 日韩免费av一区二区 | 性色av网站| 日韩手机在线看片 | 男人的天堂亚洲 | 蜜桃特黄a∨片免费观看 | 欧美日韩国产在线 | 二区av | 免费爱爱视频 | 欧美精品一二三区 | 成人免费黄视频 | 久久久久久国产 | 在线国产一区 | 久草www | 日本天堂视频在线观看 | 视频一区二区中文字幕 | 欧美日韩亚洲系列 | 91原创视频在线观看 | 亚洲精选久久 | 精品久久久久久久久久久 | 国产在线观看不卡一区二区三区 | 天天操天天干天天透 | 久久99精品久久久 | 亚洲成人免费视频在线 | 网址黄| 超碰国产在线 | www.99热 | 国产高清精品一区二区三区 | 欧美极品一区二区 | 99久久精品国产毛片 | 中文字幕视频在线 | 精品一区二区三区在线观看 | 亚洲高清三级 | 久久国产三级 | 香蕉婷婷 |