windows程式設計 程序的建立銷燬和分析
Windows程式設計:程序
程序是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動,在Windows程式設計環境下,主要由兩大元素組成:
• 一個是作業系統用來管理程序的核心物件。作業系統使用核心物件來存放關於程序的核心資訊。
• 另一個是地址空間,在地址空間囊括了所有可執行模組和動態連結庫的程式碼和資料。動態記憶體分配的空間也在其中,典型代表是執行緒堆疊和堆記憶體分配。
1程序與執行緒
程序是不活潑的。當程序開始工作的時候,它必須啟動一個在當前程序上下文中的執行緒來執行工作流程。這個執行緒被稱為主執行緒,它負責執行包含在程序的地址空間中的程式碼。同時,每一個程序可能包含多個執行緒,所有執行緒都在併發執行程序地址空間中的程式碼。這就意味著,每個執行緒都有它自己的一組CPU 暫存器和它自己的堆疊。每個程序至少擁有一個執行緒,來執行當前程序的地址空間中的程式碼。如果沒有執行緒來執行程序的地址空間中的程式碼,這就意味著程序的生命週期已經完結,系統就會將已經分配給該程序和它的地址空間全部撤銷收回。
如果,我們希望當前程序中的所有執行緒都能併發執行,那麼,作業系統就要為每一個執行緒分配相應的CPU時間。它通過輪詢方式為執行緒提供時間片(稱為量程),給使用者造成一種假象——似乎所有執行緒都是同時執行的一樣。如果計算機擁有多個CPU(例如Core2多核處理器),那麼作業系統就要使用更加複雜的演算法來實現CPU 上執行緒的負載均衡,以保證多執行緒程式可以有效的響應客戶需求。
綜上所述,當建立一個程序時,系統同時會自動建立當前程序的第一個執行緒。這個執行緒稱為主執行緒。然後,該執行緒可以建立其他的執行緒,而這些執行緒又能建立更多的執行緒。
2使用CreateProcess建立程序
Windows作業系統為使用者提供了C r e a t e P r o c e s s 函式用於程序的建立,該函式的簽名如下:
BOOL CreateProcess(
PCTSTR pszApplicationName,
PTSTR pszCommandLine,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles,
DWORD fdwCreate,
PVOID pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PPROCESS_INFORMATION ppiProcInfo);
當程式設計師呼叫CreateProcess建立程序時,系統就會建立一個程序核心物件,並將其初始使用計數置為1。這個核心物件不是程序本身,而是作業系統管理程序時使用的一個的資料結構。我們可以把它看做是程序的全部統計資訊的彙總。核心物件建立完成之後,系統會為新程序建立一個虛擬地址空間,並將可執行檔案或當前程序所需要的DLL檔案的程式碼和資料載入到該程序的地址空間中。
接下來,系統為新程序的主執行緒建立一個執行緒核心物件(其使用計數為1 )。注意,這是執行緒的核心物件,它與程序核心物件類似,是作業系統用來管理執行緒的資料結構。通過執行C/C++執行時啟動程式碼,該主執行緒便開始執行,它最終呼叫Wi n M a i n 或 m a i n 函式。如果上述的兩個步驟順利完成,C r e a t e P r o c e s s函式將返回T R U E ,宣告新程序建立成功。
值得我們注意的是:在程序初始化完成之前,CreateProcess函式就會返回TRUE。試想一下:當作業系統載入程式試圖找出所有需要的DLL,如果一個DLL無法找到,或者未能正確地初始化,那麼該程序就終止執行。由於CreateProcess已經向用戶程式返回T R U E ,因此父程序不知道出現的任何初始化問題。這在實際程式設計中需要十分小心。CreateProcess函式僅僅意味著作業系統對於新建程序的管理物件已經建立成功,它不能保證新建立的程序可以正常執行(正常執行取決於程序本身的程式碼是否滿足執行條件,與建立者無關)。
3程序的終止
通常,程序執行的終止有如下四種方式:
• 從主執行緒的入口點函式返回(推薦使用這個方法)。
• 在程序中的一個執行緒呼叫E x i t P r o c e s s函式(不推薦使用)。
• 另一個程序中的執行緒呼叫Te r m i n a t e P r o c e s s函式(不推薦使用)。
• 程序中的所有執行緒自行終止執行(作業系統強行關機)。
接下來,我們將逐一詳細說明上述四種方式。
4從主執行緒的入口點函式返回
在程式中,我們應當力求只有當主執行緒的入口點函式返回時,它的程序才終止執行。這是保證作業系統分配給執行緒的所有資源能夠得到正確清除的唯一辦法。讓主執行緒的入口點函式返回,可以確保下所有的資源都得到恰當的處理:
• 該執行緒建立的任何C++物件的解構函式將被呼叫,分配的資源被正確撤消。
• 作業系統將能正確地釋放該執行緒的堆疊記憶體。
• 系統將程序的退出程式碼(在程序的核心物件中維護)設定為入口點函式的返回值。
• 系統將程序核心物件的返回值計數遞減1。
5慎用ExitProcess函式:C++是執行在C語言之上的
當程序中的某個執行緒呼叫E x i t P r o c e s s函式時,程序便終止執行:
VOID ExitProcess(UINT fuExitCode);
該函式用於終止程序的執行,並將程序的退出程式碼設定為引數f u E x i t C o d e的值。E x i t P r o c e s s函式並不返回任何值,這是因為程序已經被終止執行。如果在呼叫E x i t P r o c e s s之後又增加了什麼程式碼,那麼新增程式碼將不可能獲得執行的機會。
當主執行緒的入口點函式( WinMain、wWinMain、main或wmain)返回時,它將返回給C/C++執行時啟動程式碼,它能正確地清除該程序使用的所有的C執行時資源。當C執行時資源被釋放之後,C執行時啟動程式碼就顯式呼叫E x i t P r o c e s s函式,並將入口點函式返回的值傳遞給它。因此,我們只需要在主執行緒入口點返回,就能終止當前程序。請注意,程序中執行的任何其他執行緒都隨著程序的終止而終止。
Windows的SDK程式設計手冊指出,程序要等到所有執行緒終止執行之後才終止執行。就作業系統而言,這是正確的。但是, C/C++執行時對應用程式採用了不同的規則,通過呼叫E x i t P r o c e s s,使得C/C++執行時啟動程式碼能夠確保主執行緒從它的入口點函式返回時,程序便終止執行,而不管程序中是否還有其他執行緒在執行。不過,如果在入口點函式中呼叫E x i t T h r e a d,而不是呼叫E x t i P r o c e s s或者僅僅是返回,那麼應用程式的主執行緒將停止執行。此時,不難發現,ExitProcess僅僅退出了當前執行緒,而非當前程序。如果當前程序中至少有一個執行緒還在執行,該程序將不會終止執行。
在呼叫E x i t P r o c e s s或E x i t T h r e a d可使程序或執行緒在函式中就終止執行。就作業系統而言,這沒有問題。程序或執行緒的所有作業系統資源都將被全部清除。但是, C/C++應用程式應該避免呼叫這些函式,因為C/C++執行時可能無法被全部清除,例如:
#include <windows.h>
#include <stdio.h>
class CMyObject
{
public:
CMyObject()
{
printf("Constructor\r\n");
}
~CMyObject()
{
printf("Destructor\r\n");
}
};
CMyObject g_GlobalObj;
void main()
{
CMyObject LocalObj;
ExitProcess(0);
}
接下來,我們轉到對應的檔案目錄下面,在專案上右鍵彈出選單,選中在檔案資源管理器中開啟資料夾,
獲得檔案的目錄,
然後開啟命令列工具,
使用cd 命令,切換到debug資料夾,執行exe檔案,
我們將會看到:
Constructor
Constructor
這個應用建立了兩個物件,一個是全域性物件,另一個是區域性物件。不過我們一定不會看到Destructor這個單詞出現, C++物件沒有被正確地撤消,因為E x i t P r o c e s s函式強制當前程序終止執行,C/C++執行時沒有機會進行呼叫解構函式釋放資源。
這告訴我們,當呼叫E x i t P r o c e s s函式必須慎重!如果在上面的程式碼中刪除了對E x i t P r o c e s s(0)這行程式碼,那麼再次執行程式,我們可以得到如下結果:
Constructor
Constructor
Destructor
Destructor
只要讓主執行緒的入口點函式返回, C/C++執行時就能夠執行清理工作,並且正確地撤消任何或所有的C++物件。
顯式呼叫E x i t P r o c e s s和E x i t T h r e a d是導致應用程式不能正確清理的常見原因。在呼叫E x i t T h r e a d時,程序可能將繼續執行,但是可能會洩漏記憶體或其他資源。
6程序終止後作業系統的工作
當程序被終止時,作業系統將完成下列工作:
1) 程序中剩餘的所有執行緒將全部被終止執行。
2) 程序指定的所有使用者物件和GDI物件均被釋放,所有核心物件均被關閉(如果沒有其他程序關聯到這些控制代碼,那麼這些核心物件將被撤消。但是,如果存在其他程序關聯到這些控制代碼, 核心物件將不會撤消)。
3) 程序的退出程式碼將從S T I L L A C T I V E改為傳遞給E x i t P r o c e s s或Te r m i n a t e P r o c e s s的程式碼。
4) 程序核心物件的狀態變成受信的狀態。程序中的其他執行緒被掛起,直到程序終止執行。
5) 程序核心物件的使用計數遞減1。
注意,程序的核心物件的壽命一定不會低於程序本身的壽命,程序核心物件的壽命卻有可能大大超過它的程序壽命。當程序終止執行時,系統能夠自動確定它的核心物件的使用計數。如果使用計數降為0,那麼沒有其他程序關聯到該物件,當程序被撤消時,核心物件也被撤消。
不過,如果系統中的另一個程序關聯到正在被撤消的程序的核心物件的時候,那麼該程序核心物件的使用計數不會降為0。當父程序忘記關閉子程序的控制代碼時,就會造成這種情況。程序核心物件維護了程序的統計資訊。即使程序已經終止執行,該資訊也是有用的。例如,當我們想要知道程序需要多少C P U時間,或者,通過呼叫G e t E x i t C o d e P r o c e s s來獲得目前已經撤消的程序的退出程式碼:
BOOL GetExitCodeProcess(HANDLE hProcess,
PDWORD pdwExitCode);
該函式就是通過檢視程序的核心物件(由h P r o c e s s引數來標識),取出核心物件的資料結構中用於標識程序的退出程式碼的成員。該退出程式碼的值在p d w E x i t C o d e引數指向的D W O R D中返回。
當呼叫G e t E x i t C o d e P r o c e s s函式時,如果程序執行尚未終止,那麼該函式就用S T I L L
因此,為了保證核心物件被正確關閉,應該及時呼叫C l o s e H a n d l e函式,告訴系統你對程序的統計資料已經不再感興趣,以便作業系統及時回收核心物件。如果程序已經終止執行,C l o s e H a n d l e將遞減核心物件的使用計數,並將它釋放。
相關視訊可以觀看
http://edu.51cto.com/course/12840.html