Windows核心编程 第四章 进程(中)
4.2 CreateProcess函數
? ? 可以用C r e a t e P r o c e s s函數創建一個進程:
?BOOL CreateProcessW(
????_In_opt_ LPCWSTR lpApplicationName,
????_Inout_opt_ LPWSTR lpCommandLine,
????_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
????_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
????_In_ BOOL bInheritHandles,
????_In_ DWORD dwCreationFlags,
????_In_opt_ LPVOID lpEnvironment,
????_In_opt_ LPCWSTR lpCurrentDirectory,
????_In_ LPSTARTUPINFOW lpStartupInfo,
????_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
? ? 當一個線程調用C r e a t e P r o c e s s時,系統就會創建一個進程內核對象,其初始使用計數是 1。該進程內核對象不是進程本身,而是操作系統管理進程時使用的一個較小的數據結構。可以將進程內核對象視為由進程的統計信息組成的一個較小的數據結構。然后,系統為新進程創建一個虛擬地址空間,并將可執行文件或任何必要的 D L L文件的代碼和數據加載到該進程的地址空間中。
? ? 然后,系統為新進程的主線程創建一個線程內核對象(其使用計數為 1) 。與進程內核對象一樣,線程內核對象也是操作系統用來管理線程的小型數據結構。通過執行 C / C + +運行期啟動代碼,該主線程便開始運行,它最終調用 Wi n M a i n、w Wi n M a i n、m a i n或w m a i n函數。如果系統成功地創建了新進程和主線程,C r e a t e P r o c e s s便返回T R U E。
? ? 注意 在進程被完全初始化之前,C r e a t e P r o c e s s返回T R U E。這意味著操作系統加載程序尚未試圖找出所有需要的 D L L。如果一個D L L無法找到,或者未能正確地初始化,那么該進程就終止運行。由于 C r e a t e P r o c e s s返回T R U E,因此父進程不知道出現的任何初始化問題。
? ? 這就是總的概述。下面各節將分別介紹C r e a t e P r o c e s s的各個參數。
4.2.1 pszApplicationName和p s z C o m m a n d L i n e
? ? p s z A p p l i c a t i o n N a m e和p s z C o m m a n d L i n e參數分別用于設定新進程將要使用的可執行文件的名字和傳遞給新進程的命令行字符串。下面首先讓我們談一談 p s z C o m m a n d L i n e參數。
????注意 請注意,p s z C o m m a n d L i n e參數的原型是P T S T R。這意味著C r e a t e P r o c e s s期望你將傳遞一個非常量字符串的地址。從內部來講, C r e a t e P r o c e s s實際上并不修改你傳遞給它的命令行字符串。不過,在C r e a t e P r o c e s s返回之前,它將該字符串恢復為它的原始形式。
? ? 這個問題很重要,因為如果命令行字符串不包含在文件映象的只讀部分中,就會出現違規訪問的問題。例如,下面的代碼就會導致違規訪問的問題,因為 Visual C++將“N O T E PA D”字符串放入了只讀內存:
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
????CreateProcess(NULL ,TEXT("NOTEPAD"),NULL ,NULL ,
FALSE ,0 ,NULL ,NULL ,&si ,&pi);
當C r e a t e P r o c e s s試圖修改該字符串時,就會發生違規訪問(較早的 Visual C++版本將該字符串放入讀/寫內存,因此調用C r e a t e P r o c e s s不會導致違規訪問的問題) 。
解決這個問題的最好辦法是在調用 C r e a t e P r o c e s s之前像下面這樣將常量字符串拷貝到臨時緩存中:
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
TCHAR szCommandLine[] = TEXT("NOTEPAD");
CreateProcess(NULL ,szCommandLine ,NULL ,NULL ,
FALSE ,0 ,NULL ,NULL ,&si ,&pi);
? ? 也可以考慮使用Visual C++的/ G f和/ G F編譯器開關,這些開關用于控制重復字符串的刪除和確定這些字符串是否被放入只讀內存部分(另外請注意, / Z I開關允許使用Visual Studio的Edit &Continue調試特性,它包含了/ G F開關的功能) 。能做的最好工作是使用/ G F編譯器開關和臨時緩存。M i c r o s o f t能做的最好事情是安裝好C r e a t e - P r o c e s s,使它能夠制作一個該字符串的臨時拷貝,這樣我們就不必進行這項操作。也許將來的Wi n d o w s版本能夠做到這一點。
? ? 另外,如果調用Windows 2000上的C r e a t e P r o c e s s的A N S I版本,就不會違規訪問,因為系統已經制作了一個命令行字符串的臨時拷貝(詳細信息請見第 2章) 。
?
? ? 可以使用p s z C o m m a n d L i n e參數設定一個完整的命令行,以便 C r e a t e P r o c e s s用來創建新進程。當C r e a t e P r o c e s s分析p s z C o m m a n d L i n e字符串時,它將查看字符串中的第一個標記,并假設該標記是想運行的可執行文件的名字。如果可執行文件的文件名沒有擴展名,便假設它的擴展名為. e x e。C r e a t e P r o c e s s也按下面的順序搜索該可執行文件:
1) 包含調用進程的. e x e文件的目錄。
2) 調用進程的當前目錄。
3) Wi n d o w s的系統目錄。
4) Wi n d o w s目錄。
5) PAT H環境變量中列出的目錄。
? ? 當然,如果文件名包含全路徑,系統將使用全路徑來查看可執行文件,并且不再搜索這些目錄。如果系統找到了可執行文件,那么它就創建一個新進程,并將可執行文件的代碼和數據映射到新進程的地址空間中。然后系統將調用 C / C + +運行期啟動例程。正如前面我們講過的那樣,C / C + +運行期啟動例程要查看進程的命令行,并將地址作為 ( w ) Wi n M a i n的p s z C m d L i n e參數傳遞給可執行文件的名字后面的第一個參數。
這一切都是在p s z A p p l i c a t i o n N a m e參數是N U L L(9 9 %以上的時候都應該屬于這種情況)時發生的。如果不傳遞N U L L,可以將地址傳遞給p s z A p p l i c a t i o n N a m e參數中包含想運行的可執行文件的名字的字符串。請注意,必須設定文件的擴展名,系統將不會自動假設文件名有一個. e x e擴展名。C r e a t e P r o c e s s假設該文件位于當前目錄中,除非文件名前面有一個路徑。如果在
當前目錄中找不到該文件,C r e a t e P r o c e s s將不會在任何其他目錄中查找該文件,它運行失敗了。
????但是,即使在 p s z A p p l i c a t i o n N a m e參數中設定了文件名, C r e a t e P r o c e s s也會將p s z C o m m a n d L i n e參數的內容作為它的命令行傳遞給新進程。例如,可以像下面這樣調用
C r e a t e P r o c e s s :
?
? ? 系統啟動N o t e p a d應用程序,但是N o t e p a d的命令行是W O R D PAD README.TXT。這種變異情況當然有些奇怪,不過這正是 C r e a t e P r o c e s s運行的樣子。這個由p s z A p p l i c a t i o n N a m e參數提供的能力實際上被添加給了C r e a t e P r o c e s s,以支持Windows 2000的P O S I X子系統。
4.2.2 psaProcess、p s a T h r e a d和b i n h e r i t H a n d l e s
? ? 若要創建一個新進程,系統必須創建一個進程內核對象和一個線程內核對象(用于進程的主線程) ,由于這些都是內核對象,因此父進程可以得到機會將安全屬性與這兩個對象關聯起來。可以使用p s a P r o c e s s和p s a T h r e a d參數分別設定進程對象和線程對象需要的安全性。可以為這些參數傳遞N U L L,在這種情況下,系統為這些對象賦予默認安全性描述符。也可以指定兩個S E C U R I T Y _ AT T R I B U T E S結構,并對它們進行初始化,以便創建自己的安全性權限,并將它們賦予進程對象和線程對象。
? ? 將S E C U R I T Y _ AT T R I B U T E S結構用于p s a P r o c e s s和p s a T h r e a d參數的另一個原因是,父進程將來生成的任何子進程都可以繼承這兩個對象句柄中的任何一個(第 3章已經介紹了內核對象句柄的繼承性的有關理論) 。
? ? 清單4 - 1顯示了一個說明內核對象繼承性的簡單程序。假設 Process A創建了Process B,方法是調用C r e a t e P r o c e s s,為p s a P r o c e s s參數傳遞一個S E C U R I T Y _ AT T R I B U T E S結構的地址,在這個結構中,b I n h e r i t H a n d l e s成員被置為T R U E。在同樣這個函數調用中,p s a T h r e a d參數指向另一個S E C U R I T Y _ AT T R I B U T E S結構,在這個結構中,b I n h e r i t H a n d l e s成員被置為FA L S E。
? ? 當系統創建Process B時,它同時指定一個進程內核對象和一個線程內核對象,并且將句柄返回給p p i P r o c I n f o參數(很快將介紹該參數)指向的結構中的Process A。這時,使用這些句柄,Process A就能夠對新創建的進程對象和線程對象進行操作。
? ? 現在,假設Process A第二次調用C r e a t e P r o c e s s函數,以便創建Process C。Process A可以決定是否為Process C賦予對Process A能夠訪問的某些內核對象進行操作的能力。B I n h e r i t H a n d l e s參數可以用于這個目的。如果b I n h e r i t H a n d l e s被置為T R U E,系統就使Process C繼承Process A中的任何可繼承句柄。在這種情況下, Process B的進程對象的句柄是可繼承的。無論C r e a t e P r o c e s s的b I n h e r i t H a n d l e s參數的值是什么,Process B的主線程對象的句柄均不能繼承。同樣,如果Process A調用C r e a t e P r o c e s s,為b I n h e r i t H a n d l e s傳遞FA L S E,那么Process C將不能繼承Process A目前使用的任何句柄。
4.2.3 fdwCreate
? ? f d w C r e a t e參數用于標識標志,以便用于規定如何來創建新進程。如果將標志逐位用 O R操作符組合起來的話,就可以設定多個標志。
? ? ? E B U G _ P R O C E S S標志用于告訴系統,父進程想要調試子進程和子進程將來生成的任何進程。本標志還告訴系統,當任何子進程(被調試進程)中發生某些事件時,將情況通知父進程(這時是調試程序) 。
? ? ? D E B U G _ O N LY _ T H I S _ P R O C E S S標志與D E B U G _ P R O C E S S標志相類似,差別在于,調試程序只被告知緊靠父進程的子進程中發生的特定事件。如果子進程生成了別的進程,那么將不通知調試程序在這些別的進程中發生的事件。
? ? ? C R E AT E _ S U S P E N D E D標志可導致新進程被創建,但是,它的主線程則被掛起。這使得父進程能夠修改子進程的地址空間中的內存,改變子進程的主線程的優先級,或者在進程有機會執行任何代碼之前將進程添加給一個作業。一旦父進程修改了子進程,父進程將允許子進程通過調用R e s u m e T h r e a d函數來執行代碼(第7章將作詳細介紹) 。
? ? ? D E TA C H E D _ P R O C E S S標志用于阻止基于C U I的進程對它的父進程的控制臺窗口的訪問,并告訴系統將它的輸出發送到新的控制臺窗口。如果基于 C U I的進程是由另一個基于C U I的進程創建的,那么按照默認設置,新進程將使用父進程的控制臺窗口(當通過命令外殼程序來運行C編譯器時,新控制臺窗口并不創建,它的輸出將被附加在現有控制臺窗口的底部) 。通過設定本標志,新進程將把它的輸出發送到一個新控制臺窗口。
? ? ? C R E AT E _ N E W _ C O N S O L E標志負責告訴系統,為新進程創建一個新控制臺窗口。如果同時設定C R E AT E _ N E W _ C O N S O L E和D E TA C H E D _ P R O C E S S標志,就會產生一個錯誤。
? ? ? C R E AT E _ N O _ W I N D O W標志用于告訴系統不要為應用程序創建任何控制臺窗口。可以使用本標志運行一個沒有用戶界面的控制臺應用程序。
? ? ? C R E AT E _ N E W _ P R O C E S S _ G R O U P標志用于修改用戶在按下 C t r l + C或C t r l + B r e a k鍵時得到通知的進程列表。如果在用戶按下其中的一個組合鍵時,你擁有若干個正在運行的C U I進程,那么系統將通知進程組中的所有進程說,用戶想要終止當前的操作。當創建一個新的C U I進程時,如果設定本標志,可以創建一個新進程組。如果該進程組中的一個進程處于活動狀態時用戶按下C t r l + C或C t r l _ B r e a k鍵,那么系統只通知用戶需要這個進程組中的進程。
? ? ? C R E AT E _ D E FA U LT _ E R R O R _ M O D E標志用于告訴系統,新進程不應該繼承父進程使用的錯誤模式(參見本章前面部分中介紹的S e t E r r o r M o d e函數) 。
? ? ? C R E AT E _ S E PA R AT E _ W O W _ V D M標志只能當你在Windows 2000上運行1 6位Wi n d o w s應用程序時使用。它告訴系統創建一個單獨的 D O S虛擬機(V D M) ,并且在該V D M中運行1 6位Wi n d o w s應用程序。按照默認設置,所有 1 6位Wi n d o w s應用程序都在單個共享的V D M中運行。在單獨的VDM 中運行應用程序的優點是,如果應用程序崩潰,它只會使單個V D M停止工作,而在別的 V D M中運行的其他程序仍然可以繼續正常運行。另外,在單獨的V D M中運行的1 6位Wi n d o w s應用程序有它單獨的輸入隊列。這意味著如果一個應用程序臨時掛起,在各個V D M中的其他應用程序仍然可以繼續接收輸入信息。運行多個V D M的缺點是,每個V D M都要消耗大量的物理存儲器。Windows 98在單個V D M中運行所有的1 6位Wi n d o w s應用程序,不能改變這種情況。
? ? ? C R E AT E _ S H A R E D _ W O W _ V D M標志只能當你在Windows 2000上運行1 6位Wi n d o w s應用程序時使用。按照默認設置,除非設定了 C R E AT E _ S E PA R AT E _ W O W _ V D M標志,否則所有 1 6位Wi n d o w s應用程序都必須在單個 V D M中運行。但是,通過在注冊表中將
H K E Y _ L O C A L _ M A C H I N E \ s y s t e m \ C u r r e n t C o n t r o l S e t \ C o n t r o l \ W O W下的D e f a u l t S e p a r a t eV D M設置為“ y e s” ,就可以改變該默認行為特性。這時, C R E AT E _ S H A R E D _W O W _ V D M標志就在系統的共享V D M中運行1 6位Wi n d o w s應用程序。
? ? ? C R E AT E _ U N I C O D E _ E N V I R O N M E N T標志用于告訴系統,子進程的環境塊應該包含U n i c o d e字符。按照默認設置,進程的環境塊包含的是A N S I字符串。
? ? ? C R E AT E _ F O R C E D O S標志用于強制系統運行嵌入1 6位O S / 2應用程序的M O S - D O S應用程序。
? ? ? C R E AT E _ B R E A K AWAY _ F R O M _ J O B標志用于使作業中的進程生成一個與作業相關聯的新進程(詳細信息見第5章) 。
f d w C r e a t e參數也可以用來設定優先級類。不過用不著這樣做,并且對于大多數應用程序來說不應該這樣做,因為系統會為新進程賦予一個默認優先級。表4 - 5顯示了各種可能的優先級類別。
? ? 這些優先級類將會影響進程中包含的線程如何相對于其他進程的線程來進行調度。詳細說明請見第7章。
注意 B E L O W _ N O R M A L _ P R I O R I T Y _ C L A S S和A B O V E _ N O R M A L _ P R I O R I T Y _ C L A S S這兩個優先級類在Windows 2000中是新類,Windows NT 4(或更早的版本)、Windows 95或Windows 98均不支持這兩個類。
4.2.4 pvEnvironment
? ? p v E n v i r o n m e n t參數用于指向包含新進程將要使用的環境字符串的存塊。在大多數情況下,為該參數傳遞N U L L,使子進程能夠繼承它的父進程正在使用的一組環境字符串。也可以使用G e t E n v i r o n m e n t S t r i n g s函數
PVOID GetEnvironmentStrings();
該函數用于獲得調用進程正在使用的環境字符串數據塊的地址。可以使用該函數返回的地址,作為C r e a t e P r o c e s s的p v E n v i r o n m e n t參數。如果為p v E n v i r o n m e n t參數傳遞N U L L,那么這正是C r e a t e P r o c e s s函數所做的操作。當不再需要該內存塊時,應該調用 F r e e E n v i r o n m e n t S t r i n g s函數將內存塊釋放:
BOOL FreeEnviromentStrings(PTSTR pszEnviromentBlock);
4.2.5 pszCurDir
? ? p s z C u r D i r參數允許父進程設置子進程的當前驅動器和目錄。如果本參數是 N U L L,則新進程的工作目錄將與生成新進程的應用程序的目錄相同。如果本參數不是 N U L L,那么p s z C u r D i r必須指向包含需要的工作驅動器和工作目錄的以 0結尾的字符串。注意,必須設定路徑中的驅動器名。
4.2.6 psiStartInfo
p s i S t a r t I n f o參數用于指向一個S TA RT U P I N F O結構:
typedef struct _STARTUPINFOW {
????DWORD ??cb;
????LPWSTR ?lpReserved;
????LPWSTR ?lpDesktop;
????LPWSTR ?lpTitle;
????DWORD ??dwX;
????DWORD ??dwY;
????DWORD ??dwXSize;
????DWORD ??dwYSize;
????DWORD ??dwXCountChars;
????DWORD ??dwYCountChars;
????DWORD ??dwFillAttribute;
????DWORD ??dwFlags;
????WORD ???wShowWindow;
????WORD ???cbReserved2;
????LPBYTE ?lpReserved2;
????HANDLE ?hStdInput;
????HANDLE ?hStdOutput;
????HANDLE ?hStdError;
} STARTUPINFOW, *LPSTARTUPINFOW;
? ? 當Wi n d o w s創建新進程時,它將使用該結構的有關成員。大多數應用程序將要求生成的應用程序僅僅使用默認值。至少應該將該結構中的所有成員初始化為零,然后將 c b成員設置為該結構的大小:
STARTUPINFO si = {sizeof(si)};
? ? 如果未能將該結構的內容初始化為零,那么該結構的成員將包含調用線程的堆棧上的任何無用信息。將該無用信息傳遞給C r e a t e P r o c e s s,將意味著有時會創建新進程,有時則不能創建新進程,完全取決于該無用信息。有一點很重要,那就是將該結構的未用成員設置為零,這樣,C r e a t e P r o c e s s就能連貫一致地運行。不這樣做是開發人員最常見的錯誤。
? ? 這時,如果想要對該結構的某些成員進行初始化,只需要在調用 C r e a t e P r o c e s s之前進行這項操作即可。我們將依次介紹每個成員。有些成員只有在子應用程序創建一個重疊窗口時才有意義,而另一些成員則只有在子應用程序執行基于 C U I的輸入和輸出時才有意義。下表描述了每個成員的作用。
?
? ? ?現在介紹d w F l a g s的成員(其實這個參數目前沒在函數定義里看到,我現在是沒有看到單獨的d w F l a g s和上面的fdwCreate而是一個新的dwCreationFlags)。該成員包含一組標志,用于修改如何來創建子進程。大多數標志只是告訴C r e a t e P r o c e s s,S TA RT U P I N F O結構的其他成員是否包含有用的信息,或者某些成員是否應該忽略。下表標出可以使用的標志及其含義。
?
? ? 另外還有兩個標志,即 S TA RT F _ F O R C E O N F E E D B A C K和S TA RT F _+F O R C E O F F F -E E D B A C K,當啟動一個新進程時,它們可以用來控制鼠標的光標。由于 Wi n d o w s支持真正的多任務搶占式運行方式,因此可以啟動一個應用程序,然后在進程初始化時,使用另一個程序。為了向用戶提供直觀的反饋信息, C r e a t e P r o c e s s能夠臨時將系統的箭頭光標改為一個新光標,即沙漏箭頭光標:
? ? 該光標表示可以等待出現某種情況,也可以繼續使用系統。當啟動另一個進程時,C r e a t e P r o c e s s函數使你能夠更好地控制光標。當設定S TA RT F _ F O R C E O F F F E E D B A C K標志時,C r e a t e P r o c e s s并不將光標改為沙漏。
? ? S TA RT F _ F O R C E O N F E E D B A C K可使C r e a t e P r o c e s s能夠監控新進程的初始化,并可根據結果來改變光標。當使用該標志來調用 C r e a t e P r o c e s s時,光標改為沙漏。過2 s后,如果新進程沒有調用G U I,CreateProcess 將光標恢復為箭頭。
? ? 如果該進程在2 s內調用了G U I,C r e a t e P r o c e s s將等待該應用程序顯示一個窗口。這必須在
? ? 進程調用G U I后5 s內發生。如果沒有顯示窗口, C r e a t e P r o c e s s就會恢復原來的光標。如果顯示了一個窗口, C r e a t e P r o c e s s將使沙漏光標繼續保留 5 s。如果某個時候該應用程序調用了G e t M e s s a g e函數,指明它完成了初始化,那么C r e a t e P r o c e s s就會立即恢復原來的光標,并且停止監控新進程。
? ? 在結束這一節內容的介紹之前,我想講一講S TA RT U P I N F O的w S h o w Wi n d o w成員。你將該成員初始化為傳遞給( w ) Wi n M a i n的最后一個參數n C m d S h o w的值。該成員顯示你想要傳遞給新進程的( w ) Wi n M a i n函數的最后一個參數n C m d S h o w的值。它是可以傳遞給S h o w Wi n d o w函數的標識符之
一。通常,n C m d S h o w的值既可以是S W _ S H O W N O R M A L,也可以是SW_ SHOWMINNOACTIVE。但是,它有時可以是S W _ S H O W D E FA U LT。
? ??當在E x p l o r e r中啟動一個應用程序時,該應用程序的 ( w ) Wi n M a i n函數被調用,而S W _ S H O W N O R M A L則作為n C m d S h o w參數來傳遞。如果為該應用程序創建了一個快捷方式,可以使用快捷方式的屬性頁來告訴系統,應用程序的窗口最初應該如何顯示。下圖顯示了運行N o t e p a d的快捷方式的屬性頁。注意,使用R u n選項的組合框,就能夠設定如何顯示N o t e p a d的窗口。
??
????當使用 E x p l o r e r來啟動該快捷方式時,E x p l o r e r會正確地準備S TA RT U P I N F O結構并調用C r e a t e P r o c e s s。這時N o t e p a d開始運行,并且為n C m d S h o w參數將S W _ S H O W M I N N O A C T I V E傳遞給它的( w ) Wi n M a i n函數。運用這樣的方法,用戶能夠很容易地啟動一個應用程序,其主窗口可以用正常狀態、最小或最大狀態進行顯示。
? ? 最后,應用程序可以調用下面的函數,以便獲取由父進程初始化的 S TA RT U P I N F O結構的拷貝。子進程可以查看該結構,并根據該結構的成員的值來改變它的行為特性。
? ? VOID GetStartupInfo(LPSTARTUPINFO pStartupInfo);
注意 雖然Wi n d o w s文檔沒有明確地說明,但是在調用G e t S t a r t I n f o函數之前,必須像下面這樣對該結構的c b成員進行初始化:
STARTUPINFO si = {sizeof(si)};
GetStartupInfo(&si);
4.2.7 ppiProcInfo
? ? p p i P r o c I n f o參數用于指向你必須指定的P R O C E S S _ I N F O R M AT I O N結構。C r e a t e P r o c e s s在返回之前要對該結構的成員進行初始化。該結構的形式如下面所示:
typedef struct _PROCESS_INFORMATION {
???HANDLE hProcess;
???HANDLE hThread;
???DWORD dwProcessId;
???DWORD dwThreadId;
}PROCESS_INFORMATION,*PPROCESS_INFORMATION,*LPPROCESS_INFORMATION;
? ? 如前所述,創建新進程可使系統建立一個進程內核對象和一個線程內核對象。在創建進程的時候,系統為每個對象賦予一個初始使用計數值 1。然后,在c r e a t e P r o c e s s返回之前,該函數打開進程對象和線程對象,并將每個對象的與進程相關的句柄放入 P R O C E S S _ I N F O R M AT I O N結構的h P r o c e s s和h T h r e a d成員中。當C r e a t e P r o c e s s在內部打開這些對象時,每個對象的使用計數就變為2。
? ? 這意味著在系統能夠釋放進程對象前,該進程必須終止運行(將使用計數遞減為 1) ,并且父進程必須調用C l o s e H a n d l e(再將使用計數遞減1,使之變為0) 。同樣,若要釋放線程對象,該線程必須終止運行,父進程必須關閉線程對象的句柄(關于釋放線程對象的詳細說明,請參見本章后面“子進程”一節的內容) 。
? ? 注意 必須關閉子進程和它的主線程的句柄,以避免在應用程序運行時泄漏資源。當然,當進程終止運行時,系統會自動消除這些泄漏現象,但是,當進程不再需要訪問子進程和它的線程時,編寫得較好的軟件能夠顯式關閉這些句柄(通過調用C l o s e H a n d l e函數來關閉) 。不能關閉這些句柄是開發人員最常犯的錯誤之一。由于某些原因,許多開發人員認為,關閉進程或線程的句柄,會促使系統撤消該進程或線程。實際情況并非如此。關閉句柄只是告訴系統,你對進程或線程的統計數據不感興趣。進程或線程將繼續運行,直到它自己終止運行。
? ? 當進程內核對象創建后,系統賦予該對象一個獨一無二的標識號,系統中的其他任何進程內核對象都不能使用這個相同的I D號。線程內核對象的情況也一樣。當一個線程內核對象創建時,該對象被賦予一個獨一無二的、系統范圍的 I D號。進程I D和線程I D共享相同的號碼池。這意味著進程和線程不可能擁有相同的 I D。另外,對象決不會被賦予 0作為其 I D。在C r e a t e P r o c e s s返回之前,它要用這些 I D填入P R O C E S S _ I N F O R M AT I O N結構的d w P r o c e s s I d和d w T h r e a d I d成員中。I D使你能夠非常容易地識別系統中的進程和線程。一些實用工具(如 Ta s kM a n a g e r)對I D使用得最多,而高效率的應用程序則使用得很少。由于這個原因,大多數應用程序完全忽略I D。
? ? 如果應用程序使用 I D來跟蹤進程和線程,必須懂得系統會立即復用進程 I D和線程I D。例如,當一個進程被創建時,系統為它指定一個進程對象,并為它賦予 I D值1 2 2。如果創建了一個新進程對象,系統不會將相同的I D賦予給它。但是,如果第一個進程對象被釋放,系統就可以將1 2 2賦予創建的下一個進程對象。記住這一點后,就能避免編寫引用不正確的進程對象或線程對象的代碼。獲取進程 I D是很容易的,保存該 I D也不難,但是,接下來你應該知道,該I D標識的進程已被釋放,新進程被創建并被賦予相同的 I D。當使用已經保存的進程 I D時,最終操作的是新進程,而不是原先獲得I D的進程。
? ? 有時,運行的應用程序想要確定它的父進程。首先應該知道只有在生成子進程時,才存在進程之間的父子關系。在子進程開始執行代碼前, Wi n d o w s不再考慮存在什么父子關系。較早的Wi n d o w s版本沒有提供讓進程查詢其父進程的函數。現在, To o l H e l p函數通過P R O C E S S E N T RY 3 2結構使得這種查詢成為可能。在這個結構中有一個 t h 3 2 P a r e n t P r o c e s s I D成員,根據文檔的說明,它能返回進程的父進程的I D。
? ? 系統無法記住每個進程的父進程的I D,但是,由于I D是被立即重復使用的,因此,等到獲得父進程的I D時,該I D可能標識了系統中一個完全不同的進程。父進程可能已經終止運行。如
果應用程序想要與它的“創建者”進行通信,最好不要使用 I D。應該定義一個持久性更好的機制,比如內核對象和窗口句柄等。
? ? 若要確保進程I D或線程I D不被重復使用,唯一的方法是保證進程或線程的內核對象不會被撤消。如果剛剛創建了一個新進程或線程,只要不關閉這些對象的句柄,就能夠保證進程對象不被撤消。一旦應用程序結束使用該 I D,那么調用C l o s e H a n d l e就可以釋放內核對象,要記住,這時使用或依賴進程 I D,對來說將不再安全。如果使用的是子進程,將無法保證父進程或父線程的有效性,除非父進程復制了它自己的進程對象或線程對象的句柄,并讓子進程繼承這些句柄。
總結
以上是生活随笔為你收集整理的Windows核心编程 第四章 进程(中)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Intel汇编语言程序设计学习-第六章
- 下一篇: Windows核心编程 第四章 进程(下