小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

一個(gè)exe可執(zhí)行程序的生與死

 看風(fēng)景D人 2014-09-07

 寫在前面:

最近在研究一個(gè)VC界面庫(kù)DuiLib,在細(xì)讀它的源碼時(shí)遇到些問題,比如
它的界面是如何繪制上去的,底層操作是如何實(shí)現(xiàn)的,就是CreateWindow和
ShowWindow又是如何實(shí)現(xiàn)的, 也不得而知, 因此我想有必要重新認(rèn)識(shí)下Win32
應(yīng)用程序的啟動(dòng)/運(yùn)行原理才好。
如題所述, 本文講的的Windows環(huán)境下exe可執(zhí)行文件的運(yùn)行原理, 這里
面涉及的知識(shí)很多,需要讀者對(duì)Windows操作系統(tǒng)(如注冊(cè)表、進(jìn)程、線程、
內(nèi)存管理、PE文件格式等) 、Windows編程(本文使用c++ 語(yǔ)言)等有所了解。
本文試圖以通俗易懂的語(yǔ)言描述, 讓更多的人看的懂, 從運(yùn)行原理上對(duì)程序的運(yùn)
行有個(gè)好的了解。
文章安排方面, 我這里是以大家都懂的main/WinMain函數(shù)執(zhí)行前, 執(zhí)行時(shí),
執(zhí)行后分為三個(gè)部分:exe程序的初始化;主函數(shù)的運(yùn)行過程;程序收尾工作
PS:本人的技術(shù)也是有限的,文章中難免會(huì)有錯(cuò)誤疏漏之處,還請(qǐng)各位高手批
評(píng)指正。轉(zhuǎn)載請(qǐng)注明出處,謝謝
本文使用的例子444.exe程序下載地址:
下載地址1:
http://183.60.157.31/file/D4/53/tzydH05zcHTmkwTlAE44iue6lUk204.zip?key=n5f6
a61fc1a1fb190d2b250dc7cd74e2ce2082ccc1b4d4178e&uid=1035291&token=l1az8k
lrmhi8bwxl49201cxedj&dir=/&name=xunlei.zip
下載地址2:
http:///file/clg0o6il#
一、exe程序的初始化
打開一個(gè)軟件是如此簡(jiǎn)單, 雙擊軟件的圖標(biāo)就是了! 但你是否想過, 當(dāng)你雙
擊那個(gè)圖標(biāo)時(shí),系統(tǒng)都做了哪些工作?為什么雙擊圖標(biāo)軟件就運(yùn)行起來了?
沒錯(cuò), 就是Shell (以Explorer.exe進(jìn)程實(shí)現(xiàn)) 。 當(dāng)你啟動(dòng)電腦進(jìn)入桌面時(shí),
系統(tǒng)創(chuàng)建Explorer.exe進(jìn)程, 而其它的進(jìn)程, 可以說都是Explorer.exe的子進(jìn)
程, 因?yàn)樗鼈兌际怯蒃xplorer.exe進(jìn)程創(chuàng)建的。 也就是說, 當(dāng)你雙擊圖標(biāo)時(shí)Shell
會(huì)偵測(cè)到這個(gè)動(dòng)作, 注冊(cè)表中有相關(guān)的項(xiàng)保存著雙擊操作的信息, 如exe文件關(guān)
聯(lián)、啟動(dòng)exe的Shell是哪個(gè)。
注冊(cè)表中保存的exe文件關(guān)聯(lián)信息
打開一個(gè)exe時(shí)指定的參數(shù)信息
指定啟動(dòng)exe程序的Shell
我們看到, 啟動(dòng)exe文件指定的Shell就是Explorer.exe啦。 因此, 我們應(yīng)該
知道了,雙擊exe文件圖標(biāo)時(shí)Explorer.exe進(jìn)程的一個(gè)線程會(huì)偵測(cè)到這個(gè)操作,
它根據(jù)注冊(cè)表中的信息取得文件名(根據(jù)%1這個(gè)參數(shù)) ,然后Explorer.exe以這
個(gè)文件名調(diào)用CreateProcess函數(shù),這個(gè)函數(shù)做了很多工作。
CreateProcess函數(shù)的定義是這樣的:
BOOLWINAPICreateProcess(
__in_optLPCTSTRlpApplicationName,
__inout_optLPTSTRlpCommandLine,
__in_optLPSECURITY_ATTRIBUTESlpProcessAttributes,
__in_optLPSECURITY_ATTRIBUTESlpThreadAttributes,
__inBOOLbInheritHandles,
__inDWORDdwCreationFlags,
__in_optLPVOIDlpEnvironment,
__in_optLPCTSTRlpCurrentDirectory,
__inLPSTARTUPINFOlpStartupInfo,
__outLPPROCESS_INFORMATIONlpProcessInformation
);
此函數(shù)的具體信息和用法在MSDN上已有詳細(xì)的描述,在此就不作介紹了
http://msdn.microsoft.com/en-us/library/ms682425(v=VS.85).aspx
那么CreateProcess函數(shù)內(nèi)部都做了哪些工作呢?別急,馬上為你一一道來!
創(chuàng)建進(jìn)程內(nèi)核對(duì)象
CreateProcess實(shí)際上是通過NtCreateProcess函數(shù)實(shí)現(xiàn)的, 此時(shí), 系統(tǒng)會(huì)創(chuàng)建
一個(gè)被稱為內(nèi)核對(duì)象的對(duì)象, 這里是進(jìn)程內(nèi)核對(duì)象。 進(jìn)程內(nèi)核對(duì)象可以看作一個(gè)
操作系統(tǒng)用來管理進(jìn)程的內(nèi)核對(duì)象, 它也是系統(tǒng)用來存放關(guān)于進(jìn)程統(tǒng)計(jì)信息的地
方(一個(gè)小的數(shù)據(jù)結(jié)構(gòu)) ,進(jìn)程內(nèi)核對(duì)象維護(hù)了一個(gè)句柄表的結(jié)構(gòu)。
當(dāng)進(jìn)程被初始化之后, 其句柄表是空的。 當(dāng)進(jìn)程內(nèi)的一線程通過指定的函數(shù)
創(chuàng)建了一個(gè)內(nèi)核對(duì)象時(shí),內(nèi)核會(huì)為對(duì)象分配一塊內(nèi)存區(qū)域并初始化這塊區(qū)域。 然
后內(nèi)核會(huì)在進(jìn)程的句柄表中查找一個(gè)空的入口, 找到之后會(huì)初始化句柄表的以索
引定位的區(qū)域。 初始化的主要過程就是填充句柄表的一個(gè)單元, 包括指定內(nèi)核對(duì)
象地址,指定訪問碼,指定標(biāo)記等。
關(guān)于句柄表的描述,可以看看<< Windows進(jìn)程內(nèi)核對(duì)象句柄表>> 這篇文章
創(chuàng)建進(jìn)程的虛擬地址空間
進(jìn)程內(nèi)核對(duì)象創(chuàng)建后, 它的引用計(jì)數(shù)被置為1。 然后系統(tǒng)為剛剛創(chuàng)建的進(jìn)程
分配4GB的進(jìn)程虛擬地址空間。 之所以是4GB, 是因?yàn)樵?2位的操作系統(tǒng)中,
一 個(gè) 指 針 長(zhǎng) 度 是4字 節(jié) , 而4字 節(jié) 指 針 的 尋 址 能 力 是 從
0x00000000~0xFFFFFFFF最大值0xFFFFFFFF表示的即為4GB大小的容量。
與虛擬地址空間相對(duì)的, 還有一個(gè)物理地址空間, 這個(gè)地址空間對(duì)應(yīng)的是真
實(shí)的物理內(nèi)存。如果你的計(jì)算機(jī)上安裝了512M大小的內(nèi)存,那么這個(gè)物理地
址空間表示的范圍是0x00000000~0x1FFFFFFF。
當(dāng)操作系統(tǒng)做虛擬地址到物理地址映射時(shí), 只能映射到這一范圍, 操作系統(tǒng)
也只會(huì)映射到這一范圍。 當(dāng)進(jìn)程創(chuàng)建時(shí), 每個(gè)進(jìn)程都會(huì)有一個(gè)自己的4GB虛擬
地址空間。 要注意的是這個(gè)4GB的地址空間是 “虛擬” 的, 并不是真實(shí)存在的,
而且每個(gè)進(jìn)程只能訪問自己虛擬地址空間中的數(shù)據(jù),無(wú)法訪問別的進(jìn)程中的數(shù)
據(jù), 通過這種方法實(shí)現(xiàn)了進(jìn)程間的地址隔離。 那是不是這4GB的虛擬地址空間
應(yīng)用程序可以隨意使用呢?很遺憾, 在Windows系統(tǒng)下, 這個(gè)虛擬地址空間被
分成了4部分:NULL指針區(qū)、用戶區(qū)、64KB禁入?yún)^(qū)、內(nèi)核區(qū)。應(yīng)用程序
能使用的只是用戶區(qū)而已,大約2GB左右(最大可以調(diào)整到3GB)。內(nèi)核區(qū)
為2GB, 內(nèi)核區(qū)保存的是系統(tǒng)線程調(diào)度、 內(nèi)存管理、 設(shè)備驅(qū)動(dòng)等數(shù)據(jù), 這部分
數(shù)據(jù)供所有的進(jìn)程共享,但應(yīng)用程序是不能直接訪問的。
虛擬地址空間如何劃分
每個(gè)進(jìn)程的虛擬地址空間都要?jiǎng)澐殖筛鱾€(gè)分區(qū)。地址空間的分區(qū)是根據(jù)操
作系統(tǒng)的基本實(shí)現(xiàn)方法來進(jìn)行的。不同的Windows內(nèi)核,其分區(qū)也略有不同。 下
圖顯 示 了 每 種 平 臺(tái) 是 如 何 對(duì) 進(jìn) 程 的 地 址 空 間 進(jìn) 行 分 區(qū) 的 。
32位Windows2000的內(nèi)核與64位Windows2000的內(nèi)核擁有大體相同的分
區(qū),差別在于分區(qū)的大小和位置有所不同。另一方面,可以看到Windows98下
的分區(qū)有著很大的不同。
初始化進(jìn)程的虛擬地址空間
進(jìn)程地址空間創(chuàng)建后, Windows的裝載器 (loader, 也稱為PE裝載器) 開始
工作。Loader會(huì)讀取exe文件的信息(PE文件) ,這里又涉及到PE文件格式的
知識(shí),如不了解,可能會(huì)對(duì)下面的理解有些難度。趕快去百度充下電啦! !
此時(shí)loader會(huì)檢查PE文件的有效性,如果PE文件有錯(cuò)誤,可能會(huì)顯示出
Thisprogramcannotberunindosmode,這樣就啟動(dòng)不了啦。沒有錯(cuò)誤了,就把
PE文件的內(nèi)容(二進(jìn)制代碼)映射到進(jìn)程的地址空間中,原則是低地址的映射
到低地址的虛擬地址空間, 高地址的映射到高地址的虛擬地址空間。 實(shí)際上映射
時(shí)是增高的地址空間,因?yàn)镻E文件中和地址空間的對(duì)齊方式大小不一樣,你可
以通過/align開關(guān)調(diào)整這個(gè)數(shù)值。
然后是讀取PE文件的導(dǎo)入地址表(ImportTable) ,這里存放有exe文件需
要導(dǎo)入的模塊文件 (DLL) , 系統(tǒng)會(huì)一一加載這些dll到進(jìn)程的地址空間中, 具體
做法是調(diào)用LoadLibrary函數(shù)加載程序代碼到某個(gè)地址,然后系統(tǒng)會(huì)映射這些代
碼到進(jìn)程的地址空間中,要知道dll只需加載一次就可映射到所有進(jìn)程的地址空
間中, 并為每個(gè)dll維護(hù)一個(gè)引用計(jì)數(shù), 當(dāng)引用計(jì)數(shù)為0時(shí), dll就從內(nèi)存中卸載
掉,釋放占用的內(nèi)存。Dll里面可能又引用了其它的dll,因此加載dll時(shí)是遞歸
形式的,直到加載完ImportTable里描述的所有dll模塊,此時(shí)進(jìn)程初始化部分
完成。
創(chuàng)建進(jìn)程的主線程
當(dāng)進(jìn)程的初始化完成后, 開始創(chuàng)建進(jìn)程的主線程, 一個(gè)進(jìn)程至少要有一個(gè)主
線程才能運(yùn)行, 可以說進(jìn)程只是充當(dāng)一個(gè)容器的作用, 而線程才是執(zhí)行用戶代碼
的載體。
線程是用CreateThread這個(gè)函數(shù)創(chuàng)建的,它的定義如下:
HANDLEWINAPICreateThread(
__in_optLPSECURITY_ATTRIBUTESlpThreadAttributes,
__inSIZE_TdwStackSize,
__inLPTHREAD_START_ROUTINElpStartAddress,
__in_optLPVOIDlpParameter,
__inDWORDdwCreationFlags,
__out_optLPDWORDlpThreadId
);
該函數(shù)的具體信息請(qǐng)看MSDN的說明:
http://msdn.microsoft.com/en-us/library/ms682453(v=VS.85).aspx
創(chuàng)建線程時(shí),也和進(jìn)程相似,系統(tǒng)會(huì)創(chuàng)建線程內(nèi)核對(duì)象,初始化線程堆棧。
線程堆棧有兩個(gè), 一個(gè)是核心堆棧, 由核心態(tài)維護(hù); 另一個(gè)是用戶堆棧, 運(yùn)行在
用戶態(tài)下。同樣的,線程的引用計(jì)數(shù)也置為1。
當(dāng)進(jìn)程、線程創(chuàng)建完成后(進(jìn)程地址空間也有了,需要的dll也都加載完畢
了) ,CreateProcess函數(shù)返回,相關(guān)的信息會(huì)保存在PROCESS_INFORMATION
結(jié)構(gòu)中,它的定義如下:
typedefstruct_PROCESS_INFORMATION{
HANDLEhProcess;
HANDLEhThread;
DWORDdwProcessId;
DWORDdwThreadId;
}PROCESS_INFORMATION,*LPPROCESS_INFORMATION;
可以看到是進(jìn)程(線程)的句柄和進(jìn)程(線程)的ID,有了句柄那樣調(diào)用
CloseHandle函數(shù)并把句柄傳入就使用引用計(jì)數(shù)減1,若引用計(jì)數(shù)為0了,就從
內(nèi)存中卸載釋放內(nèi)存。還有一個(gè)是進(jìn)程(線程)ID,這個(gè)參數(shù)可作為
OpenProcess(OpenThread)的傳入?yún)?shù)打開一個(gè)已存在的進(jìn)程(線程) 。
而其它的信息,如進(jìn)程使用的環(huán)境變量(lpEnvironment指定) ,啟動(dòng)信息則
是保存在STARTUPINFO這個(gè)結(jié)構(gòu)中,它被定義為:
typedefstruct_STARTUPINFO{
DWORDcb;
LPTSTRlpReserved;
LPTSTRlpDesktop;
LPTSTRlpTitle;
DWORDdwX;
DWORDdwY;
DWORDdwXSize;
DWORDdwYSize;
DWORDdwXCountChars;
DWORDdwYCountChars;
DWORDdwFillAttribute;
DWORDdwFlags;
WORDwShowWindow;
WORDcbReserved2;
LPBYTElpReserved2;
HANDLEhStdInput;
HANDLEhStdOutput;
HANDLEhStdError;
}STARTUPINFO,*LPSTARTUPINFO;
由于此結(jié)構(gòu)的成員較多,就不詳細(xì)介紹了,看MSDN上的解釋吧
http://msdn.microsoft.com/en-us/library/ms686331(v=VS.85).aspx
如果你想取得進(jìn)程的啟動(dòng)信息, 可以調(diào)用GetStartupInfo這個(gè)函數(shù)。 事實(shí)上,
在下面要講到的運(yùn)行期庫(kù)代碼中,就調(diào)用了此函數(shù)以取得啟動(dòng)相關(guān)的信息。
C/C++ 運(yùn)行期庫(kù)的初始化
當(dāng)進(jìn)程的主線程初始化完成后, 并且線程得到了CPU時(shí)間片, CPU把CS:IP
指向程序入口點(diǎn)(OEP) ,這里以444.exe程序(一個(gè)普通的Windows程序,加
入了一些跟蹤消息的輸出)為例。
首先使用Stud_PE工具查看444.exe的PE文件信息,如下圖所示:
可以看到444.exe的程序入口點(diǎn)是000111B3(下面那個(gè)000005B3的地址是
PE文件中的相對(duì)虛擬地址,而我們要是OEP是指映射到地址空間中的地址) 。
注意這個(gè)地址是相對(duì)虛擬地址,還要加上基址(這里是00400000)才是OEP的
地址, 所以O(shè)EP=00400000+000111B3=004111B3,這個(gè)地址相當(dāng)重要, 因?yàn)?/span>
這是程序運(yùn)行時(shí)CS:IP指向的地址,即:程序運(yùn)行的第一條指令就在這個(gè)地址
處! !
下面我們來使用PEBrowse工具反匯編下.text節(jié)的內(nèi)容,.text節(jié)就是存放代
碼(指令)的節(jié),關(guān)于節(jié)(section)的概念請(qǐng)查閱PE文件的相關(guān)資料。
從上面的圖中可以看到0x4111B3地址處是一條JMP指令,它跳轉(zhuǎn)到了
WinMainCRTStartup (地址是0x412220)這個(gè)函數(shù)的地址處執(zhí)行, 好了, 終于引
出了這個(gè)神秘的“真正的入口函數(shù)” 。
事實(shí)上,入口函數(shù)有以下4種形式:
1、mainCRTStartup(用于ANSI版本的控制臺(tái)應(yīng)用程序)
2、wmainCRTStartup(用于Unicode版本的控制臺(tái)應(yīng)用程序)
3、WinMainCRTStartup(用于ANSI版本的窗口應(yīng)用程序)
4、wWinMainCRTStartup(用于Unicode版本的窗口應(yīng)用程序)
很顯然, 我這個(gè)例子中使用的是第3種, ANSI版本的窗口應(yīng)用程序, 值得
一提的是, 我這里是使用反匯編工具查看這些信息的, 目的是讓程序運(yùn)行過程一
目了然。 但你也可以看看C/C++ 運(yùn)行期庫(kù)的源代碼文件, 相信你看了后會(huì)對(duì)程序
的運(yùn)行原理更有體會(huì)。這些源碼隨你安裝VC++6.0或VisualStudio時(shí)已經(jīng)附
帶有了,我電腦上裝的是VisualStudio2010,由于我安裝在H盤,所以在我電
腦上CRT庫(kù)源代碼文件的路徑是:
H:\ProgramFiles\MicrosoftVisualStudio10.0\VC\crt\src
而WinMainCRTStartup這個(gè)函數(shù)的定義是在crtexe.c這個(gè)文件中,你可以打
開看看是不是和我這反匯編出來的代碼是一樣的呢。
再接著看WinMainCRTStartup源碼,為了方便用XXX表示各種版本的調(diào)用
intXXXCRTStartup( void )
{
__security_init_cookie();
return__tmainCRTStartup();
}
可以看到,這個(gè)“真正的入口函數(shù)”首先調(diào)用__security_init_cookie()這個(gè)函
數(shù)完成一些安全方面的初始化, 然后是返回對(duì)__tmainCRTStartup()函數(shù)的調(diào)用,
這樣一來,全部的操作都是在這個(gè)函數(shù)中一一去完成了! !
在__tmainCRTStartup中首先調(diào)用了GetStartupInfoW函數(shù)取得父進(jìn)程創(chuàng)建
本進(jìn)程時(shí)的啟動(dòng)信息, 然后又是一系列的初始化, 其中包括C++ 構(gòu)造函數(shù)的調(diào)用,
還有靜態(tài)變量,全局變量的初始化,這些操作是在_initterm這個(gè)函數(shù)中完成的。
然后往下看,你就會(huì)發(fā)現(xiàn)(w)WinMain/(w)main函數(shù)被調(diào)用了! ! !
我們對(duì)這個(gè)函數(shù)再熟悉不過了! !我們寫程序時(shí)要寫的第一個(gè)函數(shù)就是這個(gè)
所謂的主函數(shù), 但你都看到了, 經(jīng)過這么多復(fù)雜的一系列的初始化之后, 這個(gè)函
數(shù)才最終 “被” 調(diào)用, 其中的mainret就是主函數(shù)的返回值了, 習(xí)慣于寫voidmain()
的朋友可看清楚了,從來就沒有voidmain()形式的調(diào)用,不要被潭浩強(qiáng)的書給
“誤導(dǎo)”了,呵呵。
#ifdefWPRFLAG
mainret=wWinMain(
#else
mainret=WinMain(
#endif
(HINSTANCE)&__ImageBase,
NULL,
lpszCommandLine,
StartupInfo.dwFlags&STARTF_USESHOWWINDOW
?StartupInfo.wShowWindow
:SW_SHOWDEFAULT);
#else
#ifdefWPRFLAG
__winitenv=envp;
mainret=wmain(argc,argv,envp);
#else
__initenv=envp;
mainret=main(argc,argv,envp);
#endif
本文使用的例子444.exe是一個(gè)ANSI版本的窗口程序,因此調(diào)用的是
WinMain函數(shù)了, 當(dāng)然, 既然WinMain函數(shù)的調(diào)用已經(jīng)出現(xiàn)了, 第一部分的
講解也就完畢了,下面請(qǐng)看第二部分的內(nèi)容。
二、主函數(shù)的運(yùn)行過程
終于要講主函數(shù)的運(yùn)行原理了,是否很期待呢,是否覺得第一部分的東西
太復(fù)雜了呢,其實(shí)第一部分exe程序的初始化過程是非常非常復(fù)雜和繁瑣的, 我
這里只是蜻蜓點(diǎn)水一樣大概講了下, 具體的許多細(xì)節(jié)不是查閱相關(guān)資料就能明白
的, 因?yàn)檫@是微軟的東西, 不可能讓你了解內(nèi)部的核心的東西的, 不然他們就沒
飯吃咯,呵呵。你要想了解的話也是可以去研究下的。
其實(shí)本文的重點(diǎn)也是放在這部分的, 就是我們熟悉的窗口程序的運(yùn)行原理,
知道了這個(gè)原理, 對(duì)Windows程序設(shè)計(jì)是很有幫助的, 好了, 廢話就不多說了,
請(qǐng)繼續(xù)往下看。
Windows Windows窗口程序運(yùn)行原理
不知有多少朋友一上來就是MFC,創(chuàng)建工程后,運(yùn)行向?qū)?,下一步,下?/span>
步, 一個(gè)窗口程序就出來了, 但是不知道為什么要這樣做, 你可能連個(gè)WinMain
都找不到,也不知程序是怎么運(yùn)行起來的,本文不是講MFC的原理,而是講原
生的Windows窗口程序的原理,說到MFC,本質(zhì)上是C++ 對(duì)API函數(shù)的包裝,
它的內(nèi)部實(shí)現(xiàn)離不了API函數(shù)的調(diào)用, 只要你懂了這部分講的原理, 那MFC的
原理也就好理解了。
在講原理之前,我想有必要先講下幾個(gè)概念:
?窗口:是一個(gè)可視化的對(duì)象, 窗口上一般有標(biāo)題欄,菜單欄, 最小化,最大
化,關(guān)閉按鈕,這個(gè)大家都懂的
?句柄:是一個(gè)DWORD(32位)值,它用來標(biāo)識(shí)各種不同的對(duì)象,如窗口, 圖
標(biāo), 菜單, 文件, 字體等等, 相應(yīng)的就有窗口句柄 ( HWND) , 圖標(biāo)句柄 (HICON )
菜單句柄(HMENU) ,文件句柄(HANDLE)等等
?消息:表示用戶與程序交互時(shí)產(chǎn)生的各種操作的標(biāo)識(shí),也是一個(gè)DWORD
值,有窗口消息(以WM_XXX為標(biāo)識(shí)) ,通知消息(WM_NOFITY) ,命
名消息(WM_COMMAND) ,各種控件消息等等
?窗口函數(shù):各種消息的處理程序 (Handler) , 有消息到達(dá)時(shí), 在窗口函數(shù)中
處理你想要的消息
還有些概念大家可以查找相關(guān)的資料,這里就不多說了。有了這些概念, 下
面就好辦了。
首先要知道, Windows窗口程序是基于消息的, 消息的產(chǎn)生是用戶與程序的
交互產(chǎn)生的,也可以是各種系統(tǒng)消息。消息是不斷產(chǎn)生的,許多消息連在一直,
構(gòu)成消息隊(duì)列,系統(tǒng)會(huì)維護(hù)這些消息隊(duì)列,主要有兩種,一種是系統(tǒng)消息隊(duì)列,
一種是用戶消息隊(duì)列。 由于消息不斷的產(chǎn)生, 因此產(chǎn)生一個(gè)消息循環(huán), 通常窗口
創(chuàng)建完成后會(huì)有這一句while(GetMessage(&msg,NULL,0,0,0))來獲取消息。 這
樣消息就源源不斷的產(chǎn)生, 再把消息發(fā)送到窗口, 在窗口函數(shù)中處理, 程序就能
一直運(yùn)行下去直到關(guān)閉退出。
消息是一個(gè)結(jié)構(gòu),它的定義如下:
typedefstructtagMSG{
HWNDhwnd;
UINTmessage;
WPARAMwParam;
LPARAMlParam;
DWORDtime;
POINTpt;
}MSG,*PMSG,*LPMSG;
hwnd是和消息相關(guān)的窗口, message就是消息的標(biāo)識(shí), wParam,lParam是附
加的兩個(gè)參數(shù), 在使用SendMessage或PostMessage時(shí)指定的, time表示消息被
發(fā)送的時(shí)間,pt表示消息發(fā)送時(shí)鼠標(biāo)的位置(相對(duì)于屏幕坐標(biāo)) 。
那么WinMain的執(zhí)行流程是怎樣的呢?別急,且往下看。 。 。
首先要把窗口創(chuàng)建出來, 但在創(chuàng)建窗口之前,還需要注冊(cè)窗口類, 這里的類
并不是面向?qū)ο笾械念悾?而是指定一個(gè)窗口的風(fēng)格, 樣式等等, 如下面把創(chuàng)建窗
口類的操作寫在一個(gè)函數(shù)中
ATOMMyRegisterClass(HINSTANCEhInstance)
{
WNDCLASSEXwcex;
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_MY444));
wcex.hCursor=LoadCursor(NULL,IDC_ARROW);
wcex.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName=MAKEINTRESOURCE(IDC_MY444);
wcex.lpszClassName=szWindowClass;
wcex.hIconSm=LoadIcon(wcex.hInstance,MAKEINTRESOURCE(IDI_SALL));
returnRegisterClassEx(&wcex);
}
hInstace是指應(yīng)用程序載入的模塊, win32系統(tǒng)下通常是被載入模塊的線性地
址, 還記得第一部分講的嗎, WinMain的第一個(gè)參數(shù)就是它, 它是這樣被傳入的
mainret=WinMain((HINSTANCE)&__ImageBase,。 。 。 )那這個(gè)__ImageBase又是
什么東西,看下面的圖你就明白了
這個(gè)圖是我用VS2010調(diào)試時(shí)查看內(nèi)存時(shí)截下來的,從圖中可以看出
__ImageBase應(yīng)該就是值0x00905A4D,而這4個(gè)字節(jié)剛才是PE文件的頭4個(gè)
字節(jié)(PE文件頭兩個(gè)字節(jié)是MZ標(biāo)記)它是映射到地址空間中的,& 取地址后
便是hInstance , 很顯然本例中hInstance應(yīng)該是0x008B0000, 要注意的是每次運(yùn)
行時(shí)hInstance的值都是不同的,這是被映射到地址空間中的地址。它和
HMODULE其實(shí)是一樣的,
再看WNDCLASSEX這個(gè)結(jié)構(gòu),看到有個(gè)成員lpfnWndProc,這個(gè)是指定
窗口函數(shù)的地址, 這里千萬(wàn)不能寫錯(cuò)了, 不然程序運(yùn)行不起來, 而且沒有任何錯(cuò)
誤提示。lpszClassName這個(gè)成員指定了窗口的類名,在使用spy++工具查看窗
口時(shí), 可以看到這個(gè)類名。 之后就是返回RegisterClassEx函數(shù)的調(diào)用, 這樣窗口
類就注冊(cè)好了。
接下來的工作是創(chuàng)建窗口了,創(chuàng)建窗口使用函數(shù)CreateWindow(Ex),這里也
把創(chuàng)建窗口的操作寫函數(shù)中:
BOOLInitInstance(HINSTANCEhInstance,intnCmdShow)
{
HWNDhWnd;
hInst=hInstance;
hWnd=CreateWindow(szWindowClass,szTitle,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,0,CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);
if(!hWnd)
{
returnFALSE;
}
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
returnTRUE;
}
創(chuàng)建窗口成功后會(huì)返回一個(gè)窗口句柄HWND, 這個(gè)創(chuàng)建窗口的過程其實(shí)也是
相當(dāng)復(fù)雜的,創(chuàng)建時(shí)會(huì)發(fā)送幾個(gè)消息到窗口函數(shù)中,比如WM_NCCREATE,
WM_CREATE,具體的細(xì)節(jié)下一節(jié)跟蹤消息的執(zhí)行時(shí)會(huì)有介紹。有了這個(gè)窗口
句柄后就可以做很多事了,因?yàn)樵S多函數(shù)的調(diào)用都需要傳入一個(gè)窗口句柄值, 接
下來是顯示窗口ShowWindow,這個(gè)函數(shù)也會(huì)發(fā)送幾個(gè)消息出去,而后是更新
窗口UpdateWindow,主要是發(fā)送了WM_PAINT消息,讓窗口重繪。
到了最后,就是一個(gè)消息循環(huán)了,消息循環(huán)典型的寫法如下:
MSGmsg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
只要程序在運(yùn)行, 這個(gè)循環(huán)就不會(huì)退出,程序也不會(huì)終止, 當(dāng)用戶關(guān)閉窗口
時(shí),會(huì)產(chǎn)生WM_CLOSE消息,默認(rèn)的WM_CLOSE實(shí)現(xiàn)(在DefWindowProc
中)是發(fā)送了WM_DESTROY消息,我們需要在WM_DESTROY消息中調(diào)用
PostQuitMessage函數(shù), 以便讓它發(fā)送WM_QUIT消息, 當(dāng)GetMessage函數(shù)取到
的消息是WM_QUIT消息時(shí)它會(huì)返回FALSE,這樣循環(huán)就退出了,程序也會(huì)終
于。
還有一個(gè)重要的函數(shù)是窗口函數(shù), 窗口函數(shù)是一個(gè)回調(diào)函數(shù), 就是系統(tǒng)會(huì)自
己去調(diào)用這個(gè)函數(shù), 你只要按要求寫好, 當(dāng)消息到來時(shí), 系統(tǒng)會(huì)去處理, 不用你
去處理,就是Dot'tCallMe,IWillCallYou。這個(gè)函數(shù)是在注冊(cè)窗口類時(shí)由
lpfnWndProc指定的,窗口函數(shù)的定義如下:
LRESULTCALLBACKWindowProc(
__inHWNDhwnd,
__inUINTuMsg,
__inWPARAMwParam,
__inLPARAMlParam
);
hwnd是和消息的發(fā)出相關(guān)的窗口句柄, uMsg就是發(fā)送的消息, 后兩個(gè)是附
加參數(shù)。
窗口函數(shù)的處理比如可以這樣寫:
LRESULTCALLBACKWndProc(HWNDhWnd,UINTmessage,WPARAMwParam,LPARAMlParam)
{
PAINTSTRUCTps;
HDChdc;
switch(message)
{
caseWM_PAINT:
hdc=BeginPaint(hWnd,&ps);
::Rectangle(hdc,50,100,200,400);
EndPaint(hWnd,&ps);
break ;
caseWM_DESTROY:
PostQuitMessage(0);
break ;
default :
returnDefWindowProc(hWnd,message,wParam,lParam);
}
return0;
}
這里只處理了兩條消息, 而往往我們要做的就是處理各種你想處理的消息,
因?yàn)閃indows是基于消息的嗎,所以如果要處理的消息很多,那么就會(huì)有很多
的caseWM_XXX的處理,如果用MFC的話,它把這些消息分流出去了,變成
了一些ON_XXX的處理函數(shù),MFC里面會(huì)維護(hù)一個(gè)消息路由表。
總結(jié)下它的運(yùn)行原理,是這樣的一個(gè)過程:
注冊(cè)窗口類--> 創(chuàng)建窗口--> 顯示窗口--> 更新窗口--> 消息循環(huán)(取得消息, 分
發(fā)消息) ,窗口函數(shù)對(duì)消息的處理
跟蹤運(yùn)行過程
本節(jié)用一個(gè)實(shí)例來跟蹤Windows程序的運(yùn)行過程, 也就是探討下程序啟動(dòng)時(shí)
產(chǎn)生了哪些消息,又是怎樣處理的,使用的例子還是444.exe程序,此程序也沒
有什么特別之處, 只是在調(diào)用一些函數(shù)前后輸出一些信息, 還有在窗口函數(shù)的實(shí)
現(xiàn)代碼中, 寫上幾句輸出該消息是什么消息而已, 這樣就可以對(duì)程序運(yùn)行時(shí)產(chǎn)生
消息的順序有一個(gè)認(rèn)識(shí)。
從調(diào)試的信息可以看出,0x00F00000是hInstance (其實(shí)是一個(gè)地址)的值,
0x00905A4D是該地址處的值,就是PE文件的頭幾個(gè)字節(jié)。
往下看輸出了EntryMainMethod表示主方法開始了,然后是調(diào)用
RegisterClassEx注冊(cè)窗口類,寫上EndXXX是為了看看函數(shù)被調(diào)用時(shí)會(huì)不會(huì)有
消息發(fā)出,這樣就可以在窗口函數(shù)中捕獲該消息。
注冊(cè)完窗口類,開始調(diào)用CreateWindow函數(shù)創(chuàng)建窗口,前面說過,此函數(shù)
的內(nèi)部操作是很復(fù)雜的,從圖中也可以看出來,CreateWindow函數(shù)在返回前會(huì)
發(fā)出幾個(gè)消息,首先是WM_GETMINMAXINFO (0x0024)消息,這樣看來這
個(gè)消息是窗口創(chuàng)建后的第一條消息,而WM_QUIT則是最后一條消息
(GetMessage取得此消息后返回FALSE) 。
這里要注意的是以上這些消息的發(fā)出是在消息循環(huán)之前產(chǎn)生的,因?yàn)檫@是
CreateWindow函數(shù)內(nèi)部發(fā)出的消息,而消息循環(huán)還沒開始。然后是第二條消息
WM_NCCREATE(0x0081) ,消息產(chǎn)生在WM_CREATE之前,NC是notclient
非客戶區(qū)的意思,是指標(biāo)題欄,邊框等幾個(gè)區(qū)域。
后面有三條NULL的消息, 表示什么呢, 這個(gè)我本人沒研究, 而且是僅有數(shù)
值并沒有WM_XXX的定義,所以這里用NULL代替。CreateWindow在返回前
發(fā)出的最后一條消息是WM_CREATE消息,這個(gè)消息中的lParam參數(shù)就是
CreateWindow函數(shù)中的最后一個(gè)參數(shù)傳入的,往往我們會(huì)在這里完成一些初始
化的設(shè)置,不建議攔截WM_NCCREATE消息,這個(gè)默認(rèn)DefWindowProc中會(huì)
有處理。
接著是顯示窗口和更新窗口了, ShowWindow的內(nèi)部也是做了許多工作,請(qǐng)看


在ShowWindow函數(shù)返回前會(huì)發(fā)送許多消息出去,我們看到第一條消息是
WM_SHOWWINDOW,最后一條是WM_MOVE,然后函數(shù)返回,而在
UpdateWindow中只發(fā)送了一條WM_PAINT消息,讓窗口重繪。
這些都完成之后,就進(jìn)入消息循環(huán)
這些都是消息循環(huán)和窗口函數(shù)中的處理(通過DispatchMessage將消息送入
窗口函數(shù))這樣程序就一直在運(yùn)行著,消息循環(huán)中通過GetMessage函數(shù)取得消
息以便交給窗口函數(shù)處理。 你也可以使用PeekMessage, 關(guān)于它們的區(qū)別你可以
百度下相關(guān)的資料。
當(dāng)用戶關(guān)閉窗口時(shí),我們來看看又是怎樣的一個(gè)過程呢
這 就 是 關(guān) 閉 窗 口 時(shí) 的 過 程 。 可 以 看 到 首 先 是 產(chǎn) 生了
WM_NCLBUTTONDOWN消息, 因?yàn)殛P(guān)閉按鈕是在非客戶區(qū), 然后下面有一個(gè)
WM_SYSCOMMAND消息, 這個(gè)消息是系統(tǒng)菜單的消息, 你在標(biāo)題欄上右鍵時(shí)
可以看到系統(tǒng)菜單,然后是WM_CLOSE消息,默認(rèn)的實(shí)現(xiàn)在DefWindowProc
中,我想是調(diào)用了DestroyWindow這個(gè)函數(shù),它是送出了一個(gè)WM_DESTROY
和WM_NCDESTROY消息,而我們的程序中在WM_DESTROY處理了,調(diào)用
了PostQuitMessage函數(shù),發(fā)出了WM_QUIT消息,由于GetMessage函數(shù)取到
該消息時(shí)返回FALSE了,因此循環(huán)體內(nèi)的語(yǔ)句沒有執(zhí)行,WM_QUIT消息也就
沒有被打印出來, 這樣WM_NCDESTROY就是最后一條消息 (不知還有沒有其
它消息) ,while循環(huán)結(jié)束后,最后是主函數(shù)返回return(int)msg.wParam,至此,
主函數(shù)執(zhí)行完畢。
總結(jié)下這部分的內(nèi)容,消息產(chǎn)生的順序應(yīng)該是這樣一個(gè)過程:
WM_GETMINMAXINFO(0x0024)
WM_NCCREATE(0x0081)
WM_NCCALCSIZE(0x0083)
NULL(0x0093)
NULL(0x0094)
NULL(0x0094)
WM_CREATE(0x0001)
WM_SHOWWINDOW(0x0018)
WM_WINDOWPOSCHANGING(0x0046)
WM_WINDOWPOSCHANGING(0x0046)
WM_ACTIVATEAPP(0x001C)
WM_NCACTIVATE(0x0086)
NULL(0x0093)
WM_GETICON(0x007F)
WM_GETICON(0x007F)
WM_GETICON(0x007F)
NULL(0x0093)
NULL(0x0091)
NULL(0x0092)
NULL(0x0092)
WM_ACTIVATE(0x0006)
WM_IME_SETCONTENT(0x0281)
WM_IME_NOTIFY(0x0282)
WM_SETFOCUS(0x0007)
WM_NCPAINT(0x0085)
NULL(0x0093)
NULL(0x0093)
NULL(0x0091)
NULL(0x0092)
NULL(0x0092)
WM_ERASEBKGND(0x0014)
WM_WINDOWPOSCHANGED(0x0047)
NULL(0x0093)
WM_SIZE(0x0005)
WM_MOVE(0x0003)
WM_PAINT(0x000F)
BeginMessageLoop...
WM_GETICON(0x007F)
WM_GETICON(0x007F)
WM_GETICON(0x007F)
WM_SYNCPAINT(0x0088)
###############################
#####消息循環(huán)中的其它消息######
###############################
#####下面是窗口關(guān)閉后消息######
###############################
WM_NCLBUTTONDOWN(0x00A1)
WM_CAPTURECHANGED(0x0215)
WM_SYSCOMMAND(0x0112)
WM_CLOSE(0x0010)
NULL(0x0090)
WM_WINDOWPOSCHANGING(0x0046)
WM_WINDOWPOSCHANGED(0x0047)
WM_NCACTIVATE(0x0086)
NULL(0x0093)
NULL(0x0093)
NULL(0x0091)
NULL(0x0092)
NULL(0x0092)
WM_ACTIVATE(0x0006)
WM_ACTIVATEAPP(0x001C)
WM_KILLFOCUS(0x0008)
WM_IME_SETCONTENT(0x0281)
WM_IME_NOTIFY(0x0282)
WM_DESTROY(0x0002)
WM_NCDESTROY(0x0082)
EndMessageLoop...
相信這一部分的內(nèi)容會(huì)讓你受益很多, 呵呵。這部分的內(nèi)部也結(jié)束了, 請(qǐng)接
著看第三部分的內(nèi)容。
三、程序收尾工作
WinMain函數(shù)結(jié)束之后,又發(fā)生了什么事呢。我們繼續(xù)來分析。
經(jīng)調(diào)試發(fā)現(xiàn)WinMain函數(shù)返回后回到了C/C++ 運(yùn)行期庫(kù)函數(shù), 返回值賦給了
mainret。然后是一些內(nèi)存回收清理工作,請(qǐng)看圖
這里的工作主要是調(diào)用exit函數(shù)完成的,exit函數(shù)的實(shí)現(xiàn)其實(shí)是調(diào)用doexit
函數(shù), 然后執(zhí)行一些清理工作, 包括釋放不用的指針, 被占用的內(nèi)存等等。 而后
執(zhí)行到了這里:
到這里就是退出進(jìn)程了,如果__crtCorExitProcess函數(shù)沒有成功,則后面的
ExitProcess保證成功調(diào)用。 在我調(diào)試時(shí), 調(diào)試到ExitProcess這個(gè)函數(shù)就結(jié)束了,
調(diào)試程序也退出了,可見這樣一個(gè)exe程序的執(zhí)行的生命周期也就完成了。
總結(jié)
從這三部分講的內(nèi)容來看,總結(jié)出一個(gè)exe程序的執(zhí)行過程是這樣:
1、Shell(Explorer.exe )調(diào)用CreateProcess函數(shù)激活exe程序
2、系統(tǒng)創(chuàng)建一個(gè)進(jìn)程內(nèi)核對(duì)象,引用計(jì)數(shù)置為1
3、系統(tǒng)為進(jìn)程創(chuàng)建一個(gè)4GB的進(jìn)程虛擬地址空間
4、PE裝載器把exe的代碼映射到地址空間,并查找ImportTable引入相關(guān)
的動(dòng)態(tài)鏈接庫(kù)(DLLs )
5、系統(tǒng)為進(jìn)程創(chuàng)建一個(gè)主線程,線程得到CPU后,把CS:IP指向.text節(jié)中
的程序進(jìn)入點(diǎn)(OEP) ,此處是一條JMP指令,它跳到XXXCRTStartup
函數(shù)處執(zhí)行
6、這里完成c/c++運(yùn)行期庫(kù)的一些初始化設(shè)置,包括c++ 構(gòu)造函數(shù)的調(diào)用
全局變量,靜態(tài)變量的初始化
7、調(diào)用WinMain/main函數(shù),進(jìn)入主函數(shù)
8、注冊(cè)窗口類,創(chuàng)建窗口,顯示窗口,更新窗口,進(jìn)入消息循環(huán)
9、窗口關(guān)閉,循環(huán)退出,返回到C/C++ 運(yùn)行期庫(kù)
10、完成一些清理工作
11 、最后是ExitProcess退出進(jìn)程

后話
本文的內(nèi)容至此就講完了, 希望這篇文章能給迷茫, 不知怎么學(xué)習(xí)Windows
程序設(shè)計(jì)的人一個(gè)指導(dǎo),同時(shí)也對(duì)學(xué)習(xí)MFC有一個(gè)好的認(rèn)識(shí)。
本人也不是什么高手, 只是肯去研究下這方面的技術(shù), 如果你也喜歡去研究,
喜歡技術(shù), 相信你也可以做, 也可能做的更好, 因此本文的一些錯(cuò)誤之處還請(qǐng)多
多指教。完。 。 。
PS: 本人是一個(gè)喜歡編程, 喜歡技術(shù)的人, 就把技術(shù)當(dāng)做吃飯一樣, 那么你
也是嗎?我的微博^_^http://weibo.com/zhuhuigong
作者:秋傷~~!
創(chuàng)建時(shí)間:2011/09/1400:01
完成時(shí)間:2011/09/1623:40
參考文章:
1、windows啟動(dòng)以及exe文件的加載簡(jiǎn)介
2、一個(gè)microsoft的.exe程序的啟動(dòng)過程

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多