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