Windows編程 第四回 Windows程序的生與死(下)

致歉

很抱歉,讀者看到的這篇文章將是我寫的最沒什麼條理的一篇了,由於這一塊兒內容是所有Windows程序的核心與基礎,所以我分了三回來寫。我想講 得既易懂有又豐富,無奈本人才疏學淺,每每一提筆就要查很多資料,我又嘗試着把這些資料的精華融進文章裏,但是對我來說談何容易呀。但我又想或許這些原始 資料對不同的讀者會有不同的幫助,於是我就這樣決定了:在本文開始的部分我再補充一些前兩篇文章沒講完的一點內容,在後面我就羅列一下我所收集的原始資 料,大部分是上一回文中出現的新概念和我想擴充的內容,有標號與上一回對應。我希望讀者對本文前面的部分如同前面幾篇文章一樣仔細看,對後面我羅列的資料 瞟一眼當參考就行了,能看懂多少就看多少,我不強求也不再詳細解釋了。Windows編程就是這樣你看的越多,你發現不懂的問題也就越多。難怪古人云: “生有涯而知無涯”。

還有,本文中引用資料太多,我一時也記不清楚都引用過誰的了,無法一一列舉,只得在此一併謝過被引用的資料的作者了。

好了,或許我太羅嗦了,還是開始吧。

再談程序之“死”

記得在第二回中我對程序的“死”只是一句話帶過,因爲我還沒有鋪墊好,好了現在我們可以詳細的分析一下這個過程了。

這還要從while消息循環說起,還記得GetMessage函數嗎?它是一個BOOL類型函數,當它收到WM_QUIT消息時(即消息結構體的 message成員變量爲WM_QUIT,可見註釋⑦),函數返回0,意味着消息循環結束。若收到除WM_QUIT之外的消息,函數就返回非0值了,消息 循環繼續進行。

再來研究一下WM_CLOSE消息喝和WM_DESTROY消息。不知讀者是否注意到第二回與第三回的代碼有沒有什麼不 一樣的地方?對了,第三回代碼多了(加了行69-74這一段,就是用來處理WM_CLOSE消息的)該消息是用戶單擊Close按鈕或者在程序的系統菜單 上選擇Close是發生的。在這段消息響應代碼中,我們首先彈出一個消息框(這個函數講解見下面),讓用戶確認是否結束(見圖)。如果用戶選擇“否”,則 什麼也不做;如果用戶選擇“是”,則調用DestroyWindow函數銷燬窗口,DestroyWindow 函數在銷燬窗口後會向窗口過程發送WM_DESTROY 消息(這個函數就是這個作用,參數就是要銷燬窗口的句柄,不再深入展開了)。注意,此時窗口雖然銷燬了,但應用程序並沒有退出。有不少初學者錯誤地在 WM_DESTROY消息的響應代碼中,提示用戶是否退出,而此時窗口已經銷燬了,即使用戶選擇不退出,也沒有什麼意義了。所以如果你要控制程序是否退 出,應該在WM_CLOSE消息的響應代碼中完成。對WM_CLOSE 消息的響應並不是必須的,如果應用程序沒有對該消息進行響應,系統將把這條消息傳給DefWindowProc 函數(參見行79),而DefWindowProc 函數則調用DestroyWindow 函數來默認響應這條WM_CLOSE 消息。

注:DefWindowProc 函數調用默認的窗口過程,對應用程序沒有處理的其他消息提供默認處理。對於大多數的消息,應用程序都可以直接調用DefWindowProc函數進行處 理。在編寫窗口過程時,應該將DefWindowProc 函數(其參數與窗口過程函數參數一致就行)的調用放到default 語句中(請不要忘了寫它),該函數的返回值將作爲窗口過程函數的返回值。

tip:如果一個程序在終止之前要求來自用戶的確認,那麼窗口過程就需要捕獲WM_CLOSE消息,例如上面所敘述的做法。

 

 

    

DestroyWindow函數在銷燬窗口後,會給窗口過程發送WM_DESTROY消息,我們在該消息的響應代碼中調用 PostQuitMessage 函數(見行76,用法不講了,照例子寫就行了)。PostQuitMessage函數嚮應用程序的消息隊列中投遞一條WM_QUIT 消息並返回。GetMessage函數收到WM_QUIT 消息時返回0,此時消息循環才結束,程序退出。要想讓程序正常退出,我們必須響應WM_DESTROY 消息,並在消息響應代碼中調用PostQuitMessage,嚮應用程序的消息隊列中投遞WM_QUIT 消息。PostQuitMessage函數的參數值(見代碼,通常是0)將作爲WM_QUIT 消息的wParam 參數(見註釋⑦),此時參數wParam爲0。收到WM_QUIT 消息消息循環自然終止,然後程序執行下面的語句:returnmsg.wParam ;即返回0值正常退出WinMain並終止程序。

請再讓我讓我補充一點關於程序“出生”時遺漏的信息,當CreateWindow函數創建窗口後會發出WM_CREATE消息直接給窗 口函數(怎麼消息不進消息循環,自己去註釋⑦找答案吧),讓窗口函數在此時做一些初始化工作。這下是否你會明瞭爲什麼一運行第二回的程序會先出來一個消息 框,然後再出來窗口了吧。(不知你當初是否會注意到我這樣做的目的,我這樣做是爲了說明WM_CREATE消息出現的時機,當然我寫的消息框函數不會做初 始化工作,只是截獲這個消息讓讀者看到罷了,初始化工作怎麼完成,這我們就不用管了)。

好了我們再來一次“溫故知新”吧,再回顧一下程序的一生,自己看圖吧。(來自侯捷老師的《深入淺出MFC》)

過程講解:

1程序初始化過程中調用CreateWindow,爲程序建立了一個窗口,作爲程序的屏幕舞臺。CreateWindow產生窗口之後會送出WM_CREATE直接給窗口函數,後者於是可以在此時機做些初始化動作(例如配置內存、開文件、讀初始資料...)。

2. 程序活着的過程中,不斷以GetMessage 從消息貯列中抓取消息。如果這個消息是WM_QUITGetMessage會傳回0 而結束while循環,進而結束整個程序。

3. DispatchMessage 透過Windows USER 模塊的協助與監督,把消息分派至窗口函數。消息將在該處被判別並處理。

4. 程序不斷進行2. 3. 的動作。

5. 當使用者按下系統菜單中的Close 命令項,系統送出WM_CLOSE。通常程序的窗口函數不欄截此消息,於是DefWindowProc處理它。(我們寫的函數響應了WM_CLOSE,並調用了DestroyWindow,故不會有DefWindowProc參與,如果沒寫響應WM_CLOSE的DestroyWindow函數則按侯老師這麼寫的順序看)

6. DefWindowProc 收到WM_CLOSE 後,調用DestroyWindow把窗口清除。DestroyWindow本身又會送出WM_DESTROY

7. 程序對WM_DESTROY 的標準反應是調用PostQuitMessage

8. PostQuitMessage 沒什麼其它動作,就只送出WM_QUIT消息,準備讓消息循環中的GetMessage取得,如步驟2,結束消息循環。

 

代碼改良(見行26,窗口註冊),這樣寫比原來更好。我覺得本回的前半部分我講完了,剩下的大家自己看吧,能看會看多少算多少,我就不管了。

自己看吧

LoadIcon 函數(代碼行20)

在爲 hIcon 變量賦值時,可以調用LoadIcon 函數來加載一個圖標資源,返回系統分配

給該圖標的句柄。該函數的原型聲明如下所示:

HICONLoadIcon( HINSTANCE hInstance, LPCTSTR lpIconName)

LoadIcon 函數不僅可以加載Windows系統提供的標準圖標到內存中,還可以加載由用戶自己製作的圖標資源到內存中,並返回系統分配給該圖標的句柄,請參看 MSDN 關於LoadIcon的解釋。但要注意的是,如果加載的是系統的標準圖標,那麼第一個參數必須爲NULL。LoadIcon 的第二個參數是LPCTSTR 類型,利用goto definition 命令將會發現它實際被定義成CONST CHAR*,即指向字符常量的指針,而圖標的ID 是一個整數。對於這種情況我們需要用MAKEINTRESOURCE 宏把資源ID 標識符轉換爲需要的LPCTSTR 類型。

知識點 在 VC++中,對於自定義的菜單、圖標、光標、對話框等資源,都保存在資源腳本(通常擴展名爲.rc)文件中。在VC++開發環境中,要訪問資源文件,可以 單擊左邊項目視圖窗口底部的ResourceView 選項卡,你將看到以樹狀列表形式顯示的資源項目。在任何一種資源上雙擊鼠標左鍵,將打開資源編輯器。在資源編輯器中,你可以以“所見即所得”的方式對資源 進行編輯。資源文件本身是文本文件格式,如果你瞭解資源文件的編寫格式,也可以直接使用文本編輯器對資源進行編輯。在VC++中,資源是通過標識符 (ID)來標識的,同一個ID 可以標識多個不同的資源。資源的ID 實質上是一個整數,在“resource.h”中定義爲一個宏。我們在爲資源指定ID 的時候,應該養成一個良好的習慣,即在“ID”後附加特定資源英文名稱的首字母,例如,菜單資源爲IDM_XXX(M 表示Menu),圖標資源爲IDI_XXX(I 表示Icon),按鈕資源爲IDB_XXX(B 表示Button)。採用這種命名方式,我們在程序中使用資源ID時,可以一目瞭然。

 

LoadCursor 函數(行19)

在爲hCursor 變量賦值時,可以調用LoadCursor 函數來加載一個光標資源,返回系統分配給該光標的句柄。該函數的原型聲明如下所示:

HCURSORLoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);

LoadCursor 函數除了加載的是光標外,其使用方法與LoadIcon 函數一樣。

 

GetStockObject 函數(行18)

調用 GetStockObject 函數可得到系統的標準畫刷。GetStockObject 函數的原型聲明如下所示:

HGDIOBJGetStockObject( int fnObject);

參數 fnObject 指定要獲取的對象的類型,關於該參數的取值,請參看MSDN。

GetStockObject 函數不僅可以用於獲取畫刷的句柄,還可以用於獲取畫筆、字體和調色板(後幾回會講)的句柄。由於GetStockObject 函數可以返回多種資源對象的句柄,在實際調用該函數前無法確定它返回哪一種資源對象的句柄,因此它的返回值的類型定義爲HGDIOBJ,在實際使用時,需 要進行類型轉換。例如,我們要爲hbrBackground 成員指定一個黑色畫刷的句柄,可以調用如下:

wndclass.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);

當窗口發生重繪時,系統會使用這裏指定的黑色畫刷擦除窗口的背景。

 

MessageBox函數(行60)

函數功能:

該函數創建、顯示、和操作一個消息框。消息框含有應用程序定義的消息和標題,加上預定義圖標與Push(下按)按鈕的任何組合。

函數原型:

int MessageBox(HWND hWnd,LPCTSTR lpText,LPCTSTRlpCaption,UINT UType);

參數:hWnd:標識將被創建的消息框的擁有窗口。如果此參數爲NULL,則消息框沒有擁有窗口。

   lpText:指向一個以NULL結尾的、含有將被顯示的消息的字符串的指針。

   lpCaption:指向一個以NULL結尾的、用於對話框標題的字符串的指針。

uType:指定一個決定對話框的內容和行爲的位標誌集。(具體請自行查閱MSDN或維基、百度百科,自行測試)

返回值:

如果沒有足夠的內存來創建消息框,則返回值爲零。如果函數調用成功,則返回值爲下列對話框返回的菜單項目值中的一個:

IDABORT:Abort 按鈕被選中。IDCANCEL:Cancel按鈕被選中。IDIGNORE:Ignore按鈕被選中。 IDNO:NO按鈕被選中。IDOK:OK按鈕被選中。IDRETRY:RETRY按鈕被選中。 IDYES:YES按鈕被選中。

效果圖見第二回,消息框點擊按鈕返回IDOK,本回的消息框點擊按鈕會返回什麼呢?。

 

行63-67函數,他們實現在窗口顯示文字。(具體用法以後講)

 

 ①Windows程序的開頭都可看到:

  #include <windows.h>

  WINDOWS.H是主要的頭文件,它包含了其他Windows頭文件,這些頭文件的某些也包含了其他頭文件。這些頭文件中最重要的和最基本的是:

  WINDEF.H 基本型態定義。

  WINNT.H 支援Unicode的型態定義。

  WINBASE.H Kernel函數。

  WINUSER.H 使用者界面函數。

  WINGDI.H 圖形裝置界面函數。

這些頭文件定義了Windows的所有資料型態、函數調用、資料結構和常數識別字,它們是Windows文件中的一個重要部分。

 

②另一種解釋:在Windows環境中,句柄是用來標識項目的,這些項目包括:模塊(module)、任務 (task)、實例 (instance)、文件(file)、內存塊(block of memory)、菜單(menu)、控制(control)、字體(font)、資源(resource),包括圖標(icon),光標 (cursor),字符串(string)等、GDI對象(GDI object),包括位圖(bitmap),畫刷(brush),元文件(metafile),調色板(palette),畫筆(pen),區域 (region),以及設備描述表(device context)。

   句柄可以理解爲用於指向或標識內存的一塊“資源”,這些資源如:文件(file)、內存塊(block of memory)、菜單(menu)等等。操作系統通過句柄來定位核心對象和系統資源。

 下面是一些常見的句柄類型:

                                                   
 

類型

 
 

說明

 
 

HANDLE

 
 

通用句柄類型

 
 

HWND

 
 

標識一個窗口對象

 
 

HDC

 
 

標識一個設備對象

 
 

HMENU

 
 

標識一個選單對象

 
 

HICON

 
 

標識一個圖標對象

 
 

HCURSOR

 
 

標識一個光標對象

 
 

HBRUSH

 
 

標識一個刷子對象

 
 

HPEN

 
 

標識一個筆對象

 
 

HFONT

 
 

標識一個字體對象

 
 

HINSTANCE

 
 

標識一個應用程序模塊的一個實例

 
 

HLOCAL

 
 

標識一個局部內存對象

 
 

HGLOBAL

 
 

標識一個全局內存對象

 

 

分享一篇討論句柄的文章:

計算機中的“句柄”是什麼意思?

所謂句柄實際上是一個數據,是一個Long (整長型)的數據。

句柄是WONDOWS用來標識被應用程序所建立或使用的對象的唯一整數,WINDOWS使用各種各樣的句柄標識諸如應用程序實例,窗口,控制,位圖,GDI對象等等。WINDOWS句柄有點象C語言中的文件句柄。
    從上面的定義中的我們可以看到,句柄是一個標識符,是拿來標識對象或者項目的,它就象我們的姓名一樣,每個人都會有一個,不同的人的姓名不一樣,但是,也 可能有一個名字和你一樣的人。從數據類型上來看它只是一個16位的無符號整數。應用程序幾乎總是通過調用一個WINDOWS函數來獲得一個句柄,之後其他 的WINDOWS函數就可以使用該句柄,以引用相應的對象。
    如果想更透徹一點地認識句柄,我可以告訴大家,句柄是一種指向指針的指針。我們知道,所謂指針是一種內存地址。應用程序啓動後,組成這個程序的各對象是住 留在內存的。如果簡單地理解,似乎我們只要獲知這個內存的首地址,那麼就可以隨時用這個地址訪問對象。但是,如果您真的這樣認爲,那麼您就大錯特錯了。我 們知道,Windows是一個以虛擬內存爲基礎的操作系統。在這種系統環境下,Windows內存管理器經常在內存中來回移動對象,依此來滿足各種應用程 序的內存需要。對象被移動意味着它的地址變化了。如果地址總是如此變化,我們該到哪裏去找該對象呢?
    爲了解決這個問題,Windows操作系統爲各應用程序騰出一些內存儲地址,用來專門登記各應用對象在內存中的地址變化,而這個地址(存儲單元的位置)本 身是不變的。Windows內存管理器在移動對象在內存中的位置後,把對象新的地址告知這個句柄地址來保存。這樣我們只需記住這個句柄地址就可以間接地知 道對象具體在內存中的哪個位置。這個地址是在對象裝載(Load)時由系統分配給的,當系統卸載時(Unload)又釋放給系統。
    句柄地址(穩定)→記載着對象在內存中的地址————→對象在內存中的地址(不穩定)→實際對象
    本質:WINDOWS程序中並不是用物理地址來標識一個內存塊,文件,任務或動態裝入模塊的,相反的,WINDOWS API給這些項目分配確定的句柄,並將句柄返回給應用程序,然後通過句柄來進行操作。
    但是必須注意的是程序每次從新啓動,系統不能保證分配給這個程序的句柄還是原來的那個句柄,而且絕大多數情況的確不一樣的。假如我們把進入電影院看電影看 成是一個應用程序的啓動運行,那麼系統給應用程序分配的句柄總是不一樣,這和每次電影院售給我們的門票總是不同的一個座位是一樣的道理。

③知識點在 Windows.h 中,以CS_開頭的類樣式(Class Style)標識符被定義爲16

位的常量,這些常量都只有某1 位爲1。在VC++開發環境中,利用goto definition 功能,

可以看到CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS =0x0008,

CS_NOCLOSE=0x0200,讀者可以將這些16 進制數轉換爲2 進制數,就可以發現它們

都只有1 位爲1,並且爲1 的位各不相同。用這種方式定義的標識符稱爲“位標誌”,我

們可以使用位運算操作符來組合使用這些樣式。例如,要讓窗口在水平和垂直尺寸發生

變化時發生重繪,我們可以使用位或(|)操作符將CS_HREDRAW和CS_VREDRAW

組合起來,如style=CS_HREDRAW |CS_VREDRAW。假如有一個變量具有多個樣式,

而我們並不清楚該變量都有哪些樣式,現在我們想要去掉該變量具有的某個樣式,那麼

可以先對該樣式標識符進行取反(~)操作,然後再和這個變量進行與(&)操作即可實

現。例如,要去掉先前的style 變量所具有的CS_VREDRAW 樣式,可以編寫代碼:

style=style & ~CS_VREDRAW。

在 Windows 程序中,經常會用到這種位標誌標識符,後面我們在創建窗口時用到的

窗口樣式,也是屬於位標誌標識符。

  常用樣式,作爲參考

                               
 

類型

 
 

說明

 
 

CS_HREDRAW

 
 

如果窗口的水平尺寸被改變,則重畫整個窗口

 
 

CS_VREDRAW

 
 

如果窗口的垂直尺寸被改變,則重畫整個窗口

 
 

CS_BYTEALIGNCLIENT

 
 

在字節邊界上(在X方向上)定位用戶區域的位置

 
 

CS_BYTEALIGNWINDOW

 
 

在字節邊界上(在X方向上)定位窗口的位置

 
 

CS_DBLCLKS

 
 

當連續兩次按動鼠標鍵時向窗口發送該事件的消息

 
 

CS_GLOBALCLASS

 
 

定義該窗口類是一個全局類。全局類由應用程序或庫建立,並且所有的應用程序均可使用全局類

 
 

CS_NOCLOSE

 
 

禁止系統選單中的Close選項

 

Windows下的函數
在進行Windows應用程序設計中,程序員除了需要知道有關一個函數的常用信息(例如函數的名字,近函數或遠函數,返回類型以及應如何調用)之外,同時還要知道更多的內容:一個回調函數、引出函數或是一個引入函數。
導出函數(引出函數):這個術語與一個函數如何在一個模塊中說明而在另一個模塊中被調用有關。導出函數是在一個模 塊中定義而在這個模塊之外被調用的一種函數;或是被Windows或是被另一個模塊調用。這些函數必須以一種特定的方式進行說明,並被編譯器作特殊的處 理。這樣,當它們被調用時,它們會被正確地束定到合適的數據段上。DLL爲其它模塊提供要被調用的函數,因此,每個DLL一般都帶有一個DLL庫,以便應 用程序可以合法地調用DLL中的函數。DLL庫由DLL中每個導出函數的入口點組成。整個Windows API就是由構成Windows環境的不同的模塊所引出的函數組成,這些API函數的入口點在一個名爲IMPORT.LIB的DLL庫中說明。
入函數(引入函數):在DLL中導出的函數若要能爲一個模塊調用,必須在這個模塊中將這個函數說明爲導入函數。 由此可見導出函數和導入函數表達的是從兩種角度處理同一個函數的術語:導出模塊中的一個函數使得這個函數能被其它模塊調用;調用導出函數的模塊通過導入這 個函數才能調用它。在製作Windows應用程序時,連接器自動包含一個名爲IMPORT.LIB的庫文件。這個文件允許應用程序調用Windows API中的函數。這個文件被稱爲導入庫。導入庫提供了應用程序與一個到多個DLL中可被這個應用程序調用的函數之間的連接。
回調函數:回調函數是一種特殊的導出函數,是由 Windows環境直接調用的函數。一個應用程序至少要有一個回調函數。當一條消息要交給應用程序處理時,Windows調用這個回調函數。這個函數對應 於一個活動窗口,被稱爲這個窗口的窗口函數。因爲許多應用程序至少建立一個窗口,並且Windows需要向這個窗口發送消息,所以,處理消息的函數必須由 Windows調用。在請求Windows枚舉它所維護的對象時,例如字體或窗口,Windows也要調用應用程序中的回調函數。當向Windows提出 這樣的請求時,就必須向Windows提供回調函數的地址。
由於導出函數是在不同的模塊中被調用的,也就是說,調用者的代碼段與被調用的引出函數的代碼段不在同一個段中,因此,在所開發的Windows應用程 序中,導出函數都被說明爲遠函數。爲了程序運行的效率原因,導出函數都使用Pascal調用約定,這種調用約定不同於C調用約定的地方在於:

  • 最左邊的參數先入棧:Pascal調用約定的參數進入棧的順序是函數調用中最左邊的參數先入棧。C的調用約定與此相反,它採用最右邊的參數先入棧。
  • 被調用的函數負責從展中清除參數:Pascal調用約定的函數在返回時負責清除棧中的參數;C調用約定的函數不作這種工作,而由調用者來作;這樣,當程序中調用了大量的使用C調用約定的函數時,爲清除棧中的參數,在程序中要額外地增加許多代碼。
  • 全局標識符不保持原來的大小寫(一般被爲大寫形式),也不在標識符前面加下劃線。

  爲便於程序開發活動,在Windows.h中定義了兩個類型名,用於在程序說明引出函數:

           
 

類型

 
 

說明

 
 

WINAPI

 
 

等價於FAR PASCAL,說明該函數是一個導出函數,這個類型名只用於在DLL中說明導出函數,或在應用程序中對DLL中的引出函數進行函數說明時。

 
 

CALLBACK

 
 

等價於FAR PASCAL,說明該函數是一個回調函數,它常被用在應用程序模塊中說明一個窗口函數或其它種類的回調函數。

 

回調函數另一種講法(孫鑫):

回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外一方調用的,用於對該事件或條件進行響應。回調函數實現的機制是:

(1)定義一個回調函數。

(2)提供函數實現的一方在初始化的時候,將回調函數的函數指針註冊給調用者。

(3)當特定的事件或條件發生的時候,調用者使用函數指針調用回調函數對事件進行處理。

針對 Windows 的消息處理機制,窗口過程函數被調用的過程如下:

(1)在設計窗口類的時候,將窗口過程函數的地址賦值給lpfnWndProc 成員變量。

(2)調用RegsiterClass(&wndclass)註冊窗口類,那麼系統就有了我們所編寫的窗口過程函數的地址。

(3)當應用程序接收到某一窗口的消息時,調用DispatchMessage(&msg)將消息回傳給系統。系統則利用先前註冊窗口類時得到的函數指針,調用窗口過程函數對消息進行處理。

一個 Windows 程序可以包含多個窗口過程函數,一個窗口過程總是與某一個特定的窗口類相關聯(通過WNDCLASS 結構體中的lpfnWndProc 成員變量指定),基於該窗口類創建的窗口使用同一個窗口過程。lpfnWndProc 成員變量的類型是WNDPROC,我們在VC++開發環境中使用gotodefinition 功能,可以看到WNDPROC 的定義:

typedefLRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

在這裏又出現了兩個新的數據類型 LRESULT 和CALLBACK,再次使用goto definition,可以看到它們實際上是long 和__stdcall。

從 WNDPROC 的定義可以知道,WNDPROC 實際上是函數指針類型。

注意:WNDPROC被定義爲指向窗口過程函數的指針類型,窗口過程函數的格式必須與WNDPROC 相同。

知識點 在函數調用過程中,會使用棧。__stdcall 與__cdecl是兩種不同的函數調用約定,定義了函數參數入棧的順序,由調用函數還是被調用函數將參數彈出棧,以及產生函數修飾名的方法。關於這兩個調 用約定的詳細信息,讀者可參看MSDN。對於參數個數可變的函數,例如printf,使用的是__cdecl 調用約定,Win32 的API 函數都遵循__stdcall調用約定。在VC++開發環境中,默認的編譯選項是__cdecl,對於那些需要__stdcall調用約定的函數,在聲明 時必須顯式地加上__stdcall。在Windows程序中,回調函數必須遵循__stdcall調用約定,所以我們在聲明回調函數時要使用 CALLBACK。使用CALLBACK而不是__stdcall的原因是爲了告訴我們這是一個回調函數。注意,在Windows 98 和Windows2000 下,聲明窗口過程函數時,即使不使用CALLBACK也不會出錯,但在Windows NT4.0 下,則會出錯。

⑤MAKEINTRESOURCE是一個資源名轉換的宏,

  VC的定義是(winuser.h):

  #define MAKEINTRESOURCEA(i) (LPSTR)((ULONG_PTR)((WORD)(i)))

  #define MAKEINTRESOURCEW(i)(LPWSTR)((ULONG_PTR)((WORD)(i)))

  #ifdef UNICODE

  #define MAKEINTRESOURCE MAKEINTRESOURCEW

  #else

  #define MAKEINTRESOURCE MAKEINTRESOURCEA

  #endif // !UNICODE

  這個宏是把一個數字類型轉換成指針類型的宏,它不存在釋放的問題.

  用這個宏的主要原因是有的資源是用序號定義的,而不是字符串.所以要把數字轉換成字符串指針,然後再傳遞給LoadResource之類的函數,這樣才加載了資源.

  要釋放資源(用LoadResource加載的)可以調用FreeResource函數把LoadResource返回的指針傳遞給FreeResource.

  MAKEINTRESOURCE 的作用:

是把一個"數字形ID",轉化爲"字符串".但是執行前後,輸入的數據的內容和長度是不變的!它只不過就是C語言裏面"強制類型轉換"而已.

請看 Winuser.h 代碼:

  #define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))

  #define MAKEINTRESOURCEW(i)(LPWSTR)((DWORD)((WORD)(i)))

  #ifdef UNICODE

  #define MAKEINTRESOURCE MAKEINTRESOURCEW

  #else

  #define MAKEINTRESOURCE MAKEINTRESOURCEA

  #endif // !UNICODE

  現在,再來歸納它的用法.就用FindResource來說明.(這個函數與MFC的AfxFindResourceHandle)

  HRSRC FindResource(

  HMODULE hModule, // module handle

  LPCTSTR lpName, // resource name

  LPCTSTR lpType // resource type

  );

就是lpName參數需要使用MAKEINTRESOURCE ,因爲它需要LPCTSTR類型的參數輸入.那麼,情況就很清楚了.凡涉及"資源"的API或者MFC類,在參數類型爲LPCTSTR時,就應該使用 MAKEINTRESOURCE.這是針對"資源名字"爲"數字類型"時的情況.

⑥窗口樣式一覽

                                                                               
 

類型

 
 

說明

 
 

WS_BORDER

 
 

創建一個有邊框的窗口

 
 

WS_CAPTION

 
 

創建一個有標題欄的窗口

 
 

WS_CHILDWINDOW(or WS_CHILD)

 
 

創建一個子窗口(不能與WS_POPUP一起使用)

 
 

WS_CLIPCHILDREN

 
 

當在父窗口內繪製時,把子窗口占據的區域剪切在外,即不在該區域內繪圖

 
 

WS_CLIPSIBLINGS

 
 

裁剪相互有關係的子窗口,不在被其它子窗口覆蓋的區域內繪圖,僅與WS_CHILD一起使用

 
 

WS_DISABLED

 
 

創建一個初始被禁止的窗口

 
 

WS_DLGFRAME

 
 

創建一個有雙邊框但無標題的窗口

 
 

WS_HSCROLL

 
 

創建一個帶水平滾動槓的窗口

 
 

WS_VSCROLL

 
 

創建一個帶垂直滾動槓的窗口

 
 

WS_ICONIC

 
 

創建一個初始爲圖標的窗口,僅可以與WS_OVERLAPPEDWINDOWS一起使用

 
 

WS_MAXIMIZE

 
 

創建一個最大尺寸的窗口

 
 

WS_MINIMIZE

 
 

創建一個最小尺寸的窗口(即圖標)

 
 

WS_MAXIMIZEBOX

 
 

創建一個帶有極大框的窗口

 
 

WS_MINIMIZEBOX

 
 

創建一個帶有極小框的窗口

 
 

WS_OVERLAPPED

 
 

創建一個重疊式窗口,重疊式窗口帶有標題和邊框

 
 

WS_POPUP

 
 

創建一個彈出式窗口,不能與WS_CHILD一起使用

 
 

WS_SYSMENU

 
 

窗口帶有系統選單框,僅用於帶標題欄的窗口

 
 

WS_THICKFRAME

 
 

創建一個邊框的窗口,使用戶可以直接縮放窗口

 
 

WS_VISIBLE

 
 

創建一個初始可見的窗口

 


在Windows.h中,還定義了風格WS_OVERLAPPEDWINDOW和WS_POPUPWINDOW。其中,WS_OVERLAPPEDWINDOW由下面的宏進行定義:
#define WS_OVERLAPPEDWINDOW(WS_OVERLAPPED| WS_CAPTION

| WS_SYSMENU |WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX
)
WS_POPUPWINDOW定義爲:
#define WS_POPUPWINDOW (WS_BORDER | WS_POPUP | WS_SYSMENU )
但是,在使用WS_POPUPWINDOW時,必須組合WS_CAPTION ,否則不能使系統選單(WS_SYSMENU)在窗口上可見。另外兩個窗口風格是WS_GROUP和WS_TABSTOP,這兩個窗口風格的意義在介紹對 話框時進行介紹,在介紹對話框時,還將介紹其它窗口風格。

⑦“在 Windows 程序中,消息是由MSG 結構體來表示的。MSG 結構體的定義如下(僅瞭解,結構體成員變量系統給我們填,我們不用管):

typedefstruct tagMSG {

HWNDhwnd;

UINTmessage;

WPARAMwParam;

LPARAMlParam;

DWORDtime;

POINTpt;

}MSG;

該結構體中各成員變量的含義如下:

 

第一個成員變量 hwnd 表示消息所屬的窗口。我們通常開發的程序都是窗口應用程序,一個消息一般都是與某個窗口相關聯的。例如,在某個活動窗口中按下鼠標左鍵,產生的按鍵消息就是發給該窗口的。在Windows 程序中,用HWND 類型的變量來標識窗口。

第二個成員變量 message 指定了消息的標識符。在Windows 中,消息是由一個數值來表示的,不同的消息對應不同的數值。但是由於數值不便於記憶,所以Windows 將消息對應的數值定義爲WM_XXX 宏(WM 是Window Message 的縮寫)的形式,XXX 對應某種消息的英文拼寫的大寫形式。例如,使用者將鼠標光標放在本程序的顯示區域之內,並按下鼠標左按鈕,Windows就在消息隊列中放入一個消息,該 消息的message字段等於WM_LBUTTONDOWN。鍵盤按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我們通常都 是以WM_XXX 宏的形式來使用消息的。(回憶一下我們上一回是不是見過了類似WM_XXX的消息)

提示:如果想知道WM_XXX消息對應的具體數值,可以在VisualC++開發環境中選中WM_XXX,然後單擊鼠標右鍵,在彈出菜單中選擇gotodefinition,即可看到該宏的具體定義。跟蹤或查看某個變量的定義,都可以使用這個方法。

第三、第四個成員變量wParam 和lParam,用於指定消息的附加信息。例如,當我們收到一個字符消息的時候,message 成員變量的值就是WM_CHAR,但用戶到底輸入的是什麼字符,那麼就由wParam 和lParam 來說明。wParam、lParam 表示的信息隨消息的不同而不同。如果想知道這兩個成員變量具體表示的信息,可以在MSDN 中關於某個具體消息的說明文檔查看到。讀者可以在VC++的開發環境中通過goto definition 查看一下WPARAM 和LPARAM 這兩種類型的定義,可以發現這兩種類型實際上就是unsigned int和long。

最後兩個變量分別表示消息投遞到消息隊列中的時間和鼠標的當前位置座標。

 

進隊消息和不進隊消息

其實在Windows中消息能夠被分爲“進隊的”和“不進隊的”。進隊的的消息是由Windows放入程序消息隊列中 的。在程序的消息循環中,重新返回並分配給窗口過程。不進隊的的消息在Windows調用窗口時直接送給窗口過程。也就是說,進隊的消息被“發送”給消息 隊列,而不進隊的的消息則直接“發送”給窗口過程。任何情況下,窗口過程都將獲得窗口所有的消息--包括進隊的和不進隊的。窗口過程是窗口的“消息中 心”。

進隊消息基本上是用戶輸入的結果,以擊鍵(如WM_KEYDOWN和WM_KEYUP消息)、擊鍵產生的字符(WM_CHAR)、鼠標移動 (WM_MOUSEMOVE)和鼠標鍵(WM_LBUTTONDOWN)的形式給出。進隊消息還包含時鐘消息(WM_TIMER)、刷新消息 (WM_PAINT)和退出消息(WM_QUIT)。

不進隊消息則是其它消息。在許多情況下,不進隊消息來自調用特定的Windows函數。例如,當WinMain調用CreateWindow 時,Windows將創建窗口並在處理中給窗口過程發送一個WM_CREATE消息。當WinMain調用ShowWindow時,Windows將給窗 口過程發送WM_SIZE和WM_SHOWWINDOW消息。當WinMain調用UpdateWindow時,Windows將給窗口過程發送 WM_PAINT消息。鍵盤或鼠標輸入時發出的進隊消息信號,也能在不進隊消息中出現。例如,用鍵盤或鼠標選擇了一個菜單項時,鍵盤或鼠標消息就是進隊 的,而說明菜單項已選中的WM_COMMAND消息則可能就是不進隊的。這一過程顯然很複雜,但幸運的是,其中的大部分是由Windows解決的,不關我 們的程序的事。

匈牙利表示法

許多Windows程序寫作者使用一種叫做“匈牙利表示法”的變量命名規則。這是爲了紀念傳奇性的Microsoft程序員Charles Simonyi。非常簡單,變量名以一個或者多個小寫字母開始,這些字母表示變量的數據型態。例如,szCmdLine中的sz代表“以0結尾的字符 串”,在hInstance和hPrevInstance中的h前綴表示“句柄”。在命名結構變量時,可以用結構名(或者結構名的一種縮寫)的小寫作爲變 量名的前綴,或者用作整個變量名。例如,在 WinMain函數中,msg變量是MSG類型的變量;wndcls是WNDCLASS類型的一個變量。在WinSunProc函數中,ps是一個 PAINTSTRUCT結構變量,rect是一個RECT結構變量

表 1.1 匈牙利表示法

前 綴                                            含 義

a                                                數組

b                                                布爾值(int)

by                                               無符號字符(字節)

c                                                字符(字節)

cb                                               字節記數

rgb                                              保存 RGB 顏色值的長整型

cx,cy                                             短整型(計算x,y 的長度)

dw                                              無符號長整型

fn                                               函數

h                                               句柄

i                                                整數(integer)

m_                                              類的數據成員

n                                                短整型或整型

np                                               近指針

p                                                指針

l                                                長整型

lp                                               長指針

s                                                字符串

sz                                               以零結束的字符串

tm                                              正文大小

w                                               無符號整型

x,y                                              無符號整型(表示x 或y 的座標)。

 

大寫字母標識符

在前面大家一定見過類似CS_HREDRAW,WS_MAXIMIZE的標誌符,這些標識符很有規律,都是有兩個(有的 有三個,只是我們還沒碰到)大寫字母並跟一個下劃線組成前綴。這些標識符都是在Windows的頭文件中定義的數值常量。前綴指示該常量所屬的類別。其前 綴含義見下表。

                                       

 

前綴

 
 

類別

 
 

CS

 
 

窗口類別樣式

 
 

CW

 
 

建立窗口

 
 

DT

 
 

繪製文字

 
 

IDI

 
 

圖示ID

 
 

IDC

 
 

遊標ID

 
 

MB

 
 

消息框

 
 

SND

 
 

聲音

 
 

WM

 
 

窗口消息

 
 

WS

 
 

窗口樣式

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章