Windows核心编程 第四章 进程(上)
第4章 進(jìn) 程
? ? 本章介紹系統(tǒng)如何管理所有正在運行的應(yīng)用程序。首先講述什么是進(jìn)程,以及系統(tǒng)如何創(chuàng)建進(jìn)程內(nèi)核對象,以便管理每個進(jìn)程。然后將說明如何使用相關(guān)的內(nèi)核對象來對進(jìn)程進(jìn)行操作。接著,要介紹進(jìn)程的各種不同的屬性,以及查詢和修改這些屬性所用的若干個函數(shù)。還要講述創(chuàng)建或生成系統(tǒng)中的輔助進(jìn)程所用的函數(shù)。當(dāng)然,如果不深入說明如何來結(jié)束進(jìn)程的運行,那么這樣的介紹肯定是不完整的。現(xiàn)在就來介紹進(jìn)程的有關(guān)內(nèi)容。進(jìn)程通常被定義為一個正在運行的程序的實例,它由兩個部分組成:? 一個是操作系統(tǒng)用來管理進(jìn)程的內(nèi)核對象。內(nèi)核對象也是系統(tǒng)用來存放關(guān)于進(jìn)程的統(tǒng)計
信息的地方。
? 另一個是地址空間,它包含所有可執(zhí)行模塊或 D L L模塊的代碼和數(shù)據(jù)。它還包含動態(tài)內(nèi)存分配的空間。如線程堆棧和堆分配空間。
? ? 進(jìn)程是不活潑的。若要使進(jìn)程完成某項操作,它必須擁有一個在它的環(huán)境中運行的線程,該線程負(fù)責(zé)執(zhí)行包含在進(jìn)程的地址空間中的代碼。實際上,單個進(jìn)程可能包含若干個線程,所有這些線程都“同時”執(zhí)行進(jìn)程地址空間中的代碼。為此,每個線程都有它自己的一組C P U寄存器和它自己的堆棧。每個進(jìn)程至少擁有一個線程,來執(zhí)行進(jìn)程的地址空間中的代碼。如果沒有線程來執(zhí)行進(jìn)程的地址空間中的代碼,那么進(jìn)程就沒有存在的理由了,系統(tǒng)就將自動撤消該進(jìn)程和它的地址空間。若要使所有這些線程都能運行,操作系統(tǒng)就要為每個線程安排一定的 C P U時間。它通過以一種循環(huán)方式為線程提供時間片(稱為量程) ,造成一種假象,仿佛所有線程都是
同時運行的一樣。圖4 - 1顯示了在單個C P U的計算機(jī)上是如何實現(xiàn)這種運行方式的。如果計算機(jī)擁有多個 C P U,那么操作系統(tǒng)就要使用復(fù)雜得多的算法來實現(xiàn) C P U上線程負(fù)載的平衡。
? ? 當(dāng)創(chuàng)建一個進(jìn)程時,系統(tǒng)會自動創(chuàng)建它的第一個線程,稱為主線程。然后,該線程可以創(chuàng)
建其他的線程,而這些線程又能創(chuàng)建更多的線程。
? ? Wi n d o w s 2 0 0 0 Micorsoft Windows 2000能夠在擁有多個C P U的計算機(jī)上運行。 例如,我用來撰寫本書的計算機(jī)就包含兩個處理器。Windows 2000可以在每個C P U上運行不同的線程,這樣,多個線程就真的在同時運行了。Windows 2000的內(nèi)核能夠在這種類型的系統(tǒng)上進(jìn)行所有線程的管理和調(diào)度。不必在代碼中進(jìn)行任何特定的設(shè)置就能利用多處理器提供的各種優(yōu)點。
? ? Windows 98 Windows 98只能在單處理器計算機(jī)上運行。即使計算機(jī)配有多個處理器,Wi n d o w s每次只能安排一個線程運行,而其他的處理器則處于空閑狀態(tài)。
4.1 編寫第一個Wi n d o w s應(yīng)用程序
? ? Wi n d o w s支持兩種類型的應(yīng)用程序。一種是基于圖形用戶界面( G U I)的應(yīng)用程序,另一種是基于控制臺用戶界面(C U I)的應(yīng)用程序。基于G U I的應(yīng)用程序有一個圖形前端程序。它能創(chuàng)建窗口,擁有菜單,可以通過對話框與用戶打交道,并可使用所有的標(biāo)準(zhǔn)“ Wi n d o w s”組件。Wi n d o w s配備的所有應(yīng)用程序附件(如N o t e p a d、C a l c u l a t o r和Wo r d P a d) ,幾乎都是基于G U I的應(yīng)用程序。基于控制臺的應(yīng)用程序?qū)儆谖谋静僮鞯膽?yīng)用程序。它們通常不能用于創(chuàng)建窗口或處理消息,并且它們不需要圖形用戶界面。雖然基于 C U I的應(yīng)用程序包含在屏幕上的窗口中,但是窗口只包含文本。命令外殼程序 C M D . E X E(用于Windows 2000)和COMMAND.COM (用于Windows 98)都是典型的基于C U I的應(yīng)用程序。? ? 這兩種類型的應(yīng)用程序之間的界限是非常模糊的。可以創(chuàng)建用于顯示對話框的 C U I應(yīng)用程序。例如,命令外殼程序可能擁有一個特殊的命令,使它能夠顯示一個圖形對話框,在這個對話框中,可以選定你要執(zhí)行的命令,而不必記住該外殼程序支持的各個不同的命令。也可以創(chuàng)建一個基于G U I的應(yīng)用程序,它能將文本字符串輸出到一個控制臺窗口。我常常創(chuàng)建用于建立控制臺窗口的G U I應(yīng)用程序,在這個窗口中,我可以查看應(yīng)用程序執(zhí)行時的調(diào)試信息。當(dāng)然你也可以在應(yīng)用程序中使用圖形用戶界面,而不是老式的字符界面,因為字符界面使用起來不太方便。
? ? 當(dāng)使用Microsoft Visual C++來創(chuàng)建應(yīng)用程序時,這種集成式環(huán)境安裝了許多不同的鏈接程序開關(guān),這樣,鏈接程序就可以將相應(yīng)的子系統(tǒng)嵌入產(chǎn)生的可執(zhí)行程序。用于 C U I應(yīng)用程序的鏈接程序開關(guān)是 / S U B S Y S T E M : C O N D O L E,而用于 G U I應(yīng)用程序的鏈接程序開關(guān)是S U B S Y S T E M : W I N D O W S。當(dāng)用戶運行一個應(yīng)用程序時,操作系統(tǒng)的加載程序就會查看可執(zhí)行圖形程序的標(biāo)題,并抓取該子系統(tǒng)的值。如果該值指明一個 C U I應(yīng)用程序,那么加載程序就會自動保證為該應(yīng)用程序創(chuàng)建文本控制臺窗口。
? ? 如果該值指明這是個G U I應(yīng)用程序,那么加載程序不創(chuàng)建控制臺窗口,而只是加載應(yīng)用程序。一旦應(yīng)用程序啟動運行,操作系統(tǒng)就不再考慮應(yīng)用程序擁有什么類型的用戶界面。Wi n d o w s應(yīng)用程序必須擁有一個在應(yīng)用程序啟動運行時調(diào)用的進(jìn)入點函數(shù)。可以使用的進(jìn)入點函數(shù)有4個:
? ? 操作系統(tǒng)實際上并不調(diào)用你編寫的進(jìn)入點函數(shù)。它調(diào)用的是 C / C + +運行期啟動函數(shù)。該函數(shù)負(fù)責(zé)對C / C + +運行期庫進(jìn)行初始化,這樣,就可以調(diào)用m a l l o c和f r e e之類的函數(shù)。它還能夠確保已經(jīng)聲明的任何全局對象和靜態(tài)C + +對象能夠在代碼執(zhí)行以前正確地創(chuàng)建。下面說明源代碼中可以實現(xiàn)哪個進(jìn)入點以及何時使用該進(jìn)入點(見表4 - 1 )。
? ? 鏈接程序負(fù)責(zé)在它連接可執(zhí)行文件時選擇相應(yīng)的 C / C + +運行期啟動函數(shù)。如果設(shè)定了/ S U B S Y S T E M : W I N D O W S鏈接程序開關(guān),那么該鏈接程序期望找到一個 Wi n M a i n或w Wi n m a i n函數(shù)。如果這兩個函數(shù)都不存在,鏈接程序便返回一個“未轉(zhuǎn)換的外部符號”的錯誤消息。否則,它可以分別選擇Wi n M a i n C RT S t a r t u p函數(shù)或w Wi n M a i n C RT S t a r t u p函數(shù)。
?同樣,如果設(shè)定了/ S U B S Y S T E M : C O N S O L E鏈接程序開關(guān),那么該鏈接程序便期望找到m a i n或w m a i n函數(shù),并且可以分別選擇 m a i n C RT S t a r t u p函數(shù)或w m a i n C RT S t a r t u p函數(shù)。 ? ? 同樣,如果m a i n或w m a i n都不存在,那么鏈接程序返回一條“未轉(zhuǎn)換外部符號”的消息。 ? ? 但是,人們很少知道這樣一個情況,即可以從應(yīng)用程序中全部刪除 / S U B S Y S T E M鏈接程序開關(guān)。當(dāng)這樣做的時候,鏈接程序能夠自動確定應(yīng)用程序應(yīng)該連接到哪個子系統(tǒng)。當(dāng)進(jìn)行鏈接時,鏈接程序要查看代碼中存在 4個函數(shù)(Wi n M a i n、w Wi n M a i n、m a i n或w m a i n)中的哪一個。然后確定可執(zhí)行程序應(yīng)該是哪一個子系統(tǒng),并且確定可執(zhí)行程序中應(yīng)該嵌入哪個 C / C + +啟動函數(shù)。 ? ? Wi n d o w s / Visual C++編程新手常犯的錯誤之一是,當(dāng)創(chuàng)建新的應(yīng)用程序時,不小心選擇了錯誤的應(yīng)用程序類型。例如,編程員可能創(chuàng)建一個新的 Wi n 3 2應(yīng)用程序項目,但是創(chuàng)建了一個進(jìn)入點函數(shù)m a i n。當(dāng)創(chuàng)建應(yīng)用程序時,編程員會看到一個鏈接程序錯誤消息,因為 w i n 3 2應(yīng)用程序項目設(shè)置了/ S U B S Y S T E M : W I N D O W S鏈接程序開關(guān),但是不存在Wi n M a i n或w Wi n M a i n函數(shù)。這時,編程員可以有4個選擇:
? ? ? 將m a i n函數(shù)改為Wi n M a i n。通常這不是最佳的選擇,因為編程員可能想要創(chuàng)建一個控制臺應(yīng)用程序。
? ? ? 用Visual C++創(chuàng)建一個新的Win32 控制臺應(yīng)用程序,并將現(xiàn)有的源代碼添加給新應(yīng)用程序項目。這個選項冗長而乏味,因為它好像是從頭開始創(chuàng)建應(yīng)用程序,而且必須刪除原始的應(yīng)用程序文件。
? ? ? 單擊Project Settings對話框的 L i n k選項卡,將 / S U B S Y S T E M : W I N D O W S開關(guān)改為/ S U B S Y S T E M : C O N S O L E。這是解決問題的一種比較容易的方法,很少有人知道他們只需要進(jìn)行這項操作就行了。
? ? ? 單擊Project Settings對話框的L i n k選項卡,然后全部刪除/ S U B S Y S T E M : W I N D O W S開關(guān)。這是我喜歡選擇的方法,因為它提供了最大的靈活性。現(xiàn)在,連接程序?qū)⒏鶕?jù)源代碼中實現(xiàn)的函數(shù)進(jìn)行正確的操作。
? ? 當(dāng)用Visual C++的Developer Studio創(chuàng)建新Wi n 3 2應(yīng)用程序或Wi n 3 2控制臺應(yīng)用程序項目時,我不知道為什么這沒有成為默認(rèn)設(shè)置。
? ? 所有的C / C + +運行期啟動函數(shù)的作用基本上都是相同的。它們的差別在于,它們究竟是處理A N S I字符串還是U n i c o d e字符串,以及它們在對C運行期庫進(jìn)行初始化后它們調(diào)用哪個進(jìn)入點函數(shù)。Visual C++配有C運行期庫的源代碼。可以在CR t0.c文件中找到這4個啟動函數(shù)的代碼。現(xiàn)在將啟動函數(shù)的功能歸納如下:
? ? ? 檢索指向新進(jìn)程的完整命令行的指針。
? ? ? 檢索指向新進(jìn)程的環(huán)境變量的指針。
? ? ? 對C / C + +運行期的全局變量進(jìn)行初始化。如果包含了 S t d L i b . h文件,代碼就能訪問這些變量。表4 - 1列出了這些變量。
? ? ? 對C運行期內(nèi)存單元分配函數(shù)(m a l l o c和c a l l o c)和其他低層輸入/輸出例程使用的內(nèi)存棧進(jìn)行初始化。
? ? ? 為所有全局和靜態(tài)C + +類對象調(diào)用構(gòu)造函數(shù)。當(dāng)所有這些初始化操作完成后,C / C + +啟動函數(shù)就調(diào)用應(yīng)用程序的進(jìn)入點函數(shù)。如果編寫了一個w Wi n M a i n函數(shù),它將以下面的形式被調(diào)用:
? ? 當(dāng)進(jìn)入點函數(shù)返回時,啟動函數(shù)便調(diào)用 C運行期的e x i t函數(shù),將返回值(n M a i n R e t Va l)傳遞給它。E x i t函數(shù)負(fù)責(zé)下面的操作:
? ? ? 調(diào)用由_ o n e x i t函數(shù)的調(diào)用而注冊的任何函數(shù)。
? ? ? 為所有全局的和靜態(tài)的C + +類對象調(diào)用析構(gòu)函數(shù)。
? ? ? 調(diào)用操作系統(tǒng)的E x i t P r o c e s s函數(shù),將n M a i n R e t Va l傳遞給它。這使得該操作系統(tǒng)能夠撤消進(jìn)程并設(shè)置它的e x i t代碼。
? ? 表4 - 2顯示了程序能夠使用的C / C + +運行期全局變量。
4.1.1 進(jìn)程的實例句柄
? ? 加載到進(jìn)程地址空間的每個可執(zhí)行文件或 D L L文件均被賦予一個獨一無二的實例句柄。可執(zhí)行文件的實例作為( w ) Wi n M a i n的第一個參數(shù)h i n s t E x e來傳遞。對于加載資源的函數(shù)調(diào)用來說,
通常都需要該句柄的值。例如,若要從可執(zhí)行文件的映象來加載圖標(biāo)資源,需要調(diào)用下面這個
函數(shù):
? ? L o a d I c o n的第一個參數(shù)用于指明哪個文件(可執(zhí)行文件或D L L文件)包含你想加載的資源。許多應(yīng)用程序在全局變量中保存( w ) Wi n M a i n的h i n s t E x e參數(shù),這樣,它就很容易被所有可執(zhí)行文件的代碼訪問。
? ? Platform SDK文檔中說,有些函數(shù)需要H M O D U L E類型的一個參數(shù)。它的例子是下面所示
的G e t M o d u l e F i l e N a m e函數(shù):
? ? 注意 實際情況說明,H M O D U L E與H I N S TA N C E是完全相同的對象。如果函數(shù)的文檔指明需要一個H M O D U L E,那么可以傳遞一個H I N S TA N C E,反過來,如果需要一個H I N S TA N C E,也可以傳遞一個H M O D U L E。之所以存在兩個數(shù)據(jù)類型,原因是在1 6位Wi n d o w s中,H M O D U L E和H I N S TA N C E用于標(biāo)識不同的東西。 ? ? ( w ) Wi n M a i n的h i n s t E x e參數(shù)的實際值是系統(tǒng)將可執(zhí)行文件的映象加載到進(jìn)程的地址空間時使用的基本地址空間。例如,如果系統(tǒng)打開了可執(zhí)行文件并且將它的內(nèi)容加載到地址0 x 0 0 4 0 0 0 0 0中,那么( w ) Wi n M a i n的h i n s t E x e參數(shù)的值就是0 x 0 0 4 0 0 0 0 0。 ? ? 可執(zhí)行文件的映像加載到的基地址是由鏈接程序決定的。不同的鏈接程序可以使用不同的默認(rèn)基地址。Visual C++鏈接程序使用的默認(rèn)基地址是0 x 0 0 4 0 0 0 0 0,因為這是在運行Wi n d o w s9 8時可執(zhí)行文件的映象可以加載到的最低地址。可以改變應(yīng)用程序加載到的基地址,方法是使用M i c r o s o f t的鏈接程序中的/ B A S E : a d d r e s s鏈接程序開關(guān)。
? ? 如果你想在Wi n d o w s上加載的可執(zhí)行文件的基地址小于0 x 0 0 4 0 0 0 0 0,那么Windows 98加載程序必須將可執(zhí)行文件重新加載到另一個地址。這會增加加載應(yīng)用程序所需的時間,不過,這樣一來,至少該應(yīng)用程序能夠運行。如果開發(fā)的應(yīng)用程序?qū)⒁瑫r在 Windows 98和Wi n d o w s2 0 0 0上運行,應(yīng)該確保應(yīng)用程序的基地址是0 x 0 0 4 0 0 0 0 0或者大于這個地址。
? ? 下面的G e t M o d u l e H a n d l e函數(shù)返回可執(zhí)行文件或D L L文件加載到進(jìn)程的地址空間時所用的句柄/基地址: ? ?? ? ? 當(dāng)調(diào)用該函數(shù)時,你傳遞一個以 0結(jié)尾的字符串,用于設(shè)定加載到調(diào)用進(jìn)程的地址空間的可執(zhí)行文件或 D L L文件的名字。如果系統(tǒng)找到了指定的可執(zhí)行文件或 D L L文件名,G e t M o d u l e H a n d l e便返回該可執(zhí)行文件或D L L文件映象加載到的基地址。如果系統(tǒng)沒有找到該文件,則返回 N U L L。也可以調(diào)用 G e t M o d u l e H a n d l e,為 p s z M o d u l e參數(shù)傳遞 N U L L,G e t M o d u l e H a n d l e返回調(diào)用的可執(zhí)行文件的基地址。這正是 C運行期啟動代碼調(diào)用 ( w ) Wi n M a i n函數(shù)時該代碼執(zhí)行的操作。 ? ? 請記住G e t M o d u l e H a n d l e函數(shù)的兩個重要特性。首先,它只查看調(diào)用進(jìn)程的地址空間。如果調(diào)用進(jìn)程不使用常用的對話框函數(shù),那么調(diào)用G e t M o d u l e H a n d l e并為它傳遞“C o m D l g 3 2”后,就會返回 N U L L,盡管 C o m D l g 3 2 . d l l可能加載到了其他進(jìn)程的地址空間。第二,調(diào)用G e t M o d u l e H a n d l e并傳遞N U L L值,就會返回進(jìn)程的地址空間中可執(zhí)行文件的基地址。因此,即使通過包含在D L L中的代碼來調(diào)用(N U L L) ,返回的值也是可執(zhí)行文件的基地址,而不是D L L文件的基地址。
4.1.2 進(jìn)程的前一個實例句柄
? ? 如前所述,C / C + +運行期啟動代碼總是將N U L L傳遞給( w ) Wi n M a i n的h i n s t E x e P r e v參數(shù)。該參數(shù)用在1 6位Wi n d o w s中,并且保留了( w ) Wi n M a i n的一個參數(shù),目的僅僅是為了能夠容易地轉(zhuǎn)用1 6位Wi n d o w s應(yīng)用程序。決不應(yīng)該在代碼中引用該參數(shù)。由于這個原因,我總是像下面這樣編寫( w ) Wi n M a i n函數(shù):4.1.3 進(jìn)程的命令行
? ? 當(dāng)一個新進(jìn)程創(chuàng)建時,它要傳遞一個命令行。該命令行幾乎永遠(yuǎn)不會是空的,至少用于創(chuàng)建新進(jìn)程的可執(zhí)行文件的名字是命令行上的第一個標(biāo)記。但是在后面介紹 C r e a t e P r o c e s s函數(shù)時我們將會看到,進(jìn)程能夠接收由單個字符組成的命令行,即字符串結(jié)尾處的零。當(dāng) C運行期的啟動代碼開始運行的時候,它要檢索進(jìn)程的命令行,跳過可執(zhí)行文件的名字,并將指向命令行其余部分的指針傳遞給Wi n M a i n的p s z C m d L i n e參數(shù)。值得注意的是,p s z C m d L i n e參數(shù)總是指向一個A N S I字符串。但是,如果將Wi n M a i n改為w Wi n M a i n,就能夠訪問進(jìn)程的U n i c o d e版本命令行。? ? 應(yīng)用程序可以按照它選擇的方法來分析和轉(zhuǎn)換命令行字符串。實際上可以寫入 p s z C m d L i n e參數(shù)指向的內(nèi)存緩存,但是在任何情況下都不應(yīng)該寫到緩存的外面去。我總是將它視為只讀緩存。如果我想修改命令行,首先我要將命令行拷貝到應(yīng)用程序的本地緩存中,然后再修改本地緩存。
? ? 也可以獲得一個指向進(jìn)程的完整命令行的指針,方法是調(diào)用G e t C o m m a n d L i n e函數(shù):
? ? PTSTR GetCommandLine(); ? ? 該函數(shù)返回一個指向包含完整命令行的緩存的指針,該命令行包括執(zhí)行文件的完整路徑名。
? ? 許多應(yīng)用程序常常擁有轉(zhuǎn)換成它的各個標(biāo)記的命令行。使用全局性 _ _ a rg c(或_ _ w a rg v)變量,應(yīng)用程序就能訪問命令行的各個組成部分。下面這個函數(shù) C o m m a n d L i n e To A rg v W將U n i c o d e字符串分割成它的各個標(biāo)記:
? ? PWSTR CommandLineToArgvW(PWSTR pszCmdLine, int * pNumArgs); ? ? 正如該函數(shù)名的結(jié)尾處的 W所暗示的那樣,該函數(shù)只存在于 U n i c o d e版本中(W是英文單詞‘Wi d e’的縮寫) 。第一個參數(shù)p s z C m d L i n e指向一個命令行字符串。這通常是較早時調(diào)用G e t C o m m a n d L i n e W而返回的值。P N u m A rg s參數(shù)是個整數(shù)地址,該整數(shù)被設(shè)置為命令行中的參數(shù)的數(shù)目。 ? ? C o m m a n d L i n e To A rg v W將地址返回給一個U n i c o d e字符串指針的數(shù)組。C o m m a n e L i n e To A rg v W負(fù)責(zé)在內(nèi)部分配內(nèi)存。大多數(shù)應(yīng)用程序不釋放該內(nèi)存,它們在進(jìn)程運行終止時依靠操作系統(tǒng)來釋放內(nèi)存。這是完全可行的。但是如果想要自己來釋放內(nèi)存,正確的方法是像下面這樣調(diào)用H e a p F r e e函數(shù):
4.1.4 進(jìn)程的環(huán)境變量
? ? 每個進(jìn)程都有一個與它相關(guān)的環(huán)境塊。環(huán)境塊是進(jìn)程的地址空間中分配的一個內(nèi)存塊。每個環(huán)境塊都包含一組字符串,其形式如下:
? ? 每個字符串的第一部分是環(huán)境變量的名字,后跟一個等號,等號后面是要賦予變量的值。環(huán)境塊中的所有字符串都必須按環(huán)境變量名的字母順序進(jìn)行排序。 ? ? 由于等號用于將變量名與變量的值分開,因此等號不能是變量名的一部分。另外,變量中的空格是有意義的。例如,如果聲明下面兩個變量,然后將 X Y Z的值與A B C的值進(jìn)行比較,那么系統(tǒng)將報告稱,這兩個變量是不同的,因為緊靠著等號的前面或后面的任何空格均作為比較時的條件被考慮在內(nèi)。
XYZ = Windows (Notice the space after the equal sign.) ABC = Windows 例如,如果將下面兩個字符串添加給環(huán)境塊,后面帶有空格的環(huán)境變量 X Y Z包含H o m e,而沒有空格的環(huán)境變量X Y Z則包含Wo r k。
? ? XYZ = Home(Notice the sapce before the equal sign.) ? ? XYZ = Word ? ? 最后,必須將一個0字符置于所有環(huán)境變量的結(jié)尾處,以表示環(huán)境塊的結(jié)束。
? ? Wi n d o w s 9 8 若要為 Windows 98創(chuàng)建一組初始環(huán)境變量,必須修改系統(tǒng)的A u t o E x e c . b a t文件,將一系列S E T行放入該文件。每個S E T行都必須采用下面的形式: ? ? SET VarName = VarValue ? ? 當(dāng)重新引導(dǎo)系統(tǒng)時,A u t o E x e c . b a t文件的內(nèi)容被分析,設(shè)置的任何環(huán)境變量均可供在Windows 98會話期間啟動的任何進(jìn)程使用。
? ? Windows 2000 ?當(dāng)用戶登錄到Windows 2000中時,系統(tǒng)創(chuàng)建一個外殼進(jìn)程并將一組環(huán)境字符串與它相關(guān)聯(lián)。通過查看注冊表中的兩個關(guān)鍵字,系統(tǒng)可以獲得一組初始環(huán)境字符串。
? ? 第一個關(guān)鍵字包含一個適用于系統(tǒng)的所有環(huán)境變量的列表:
? ? 第二個關(guān)鍵字包含適用于當(dāng)前登錄的用戶的所有環(huán)境變量的列表:
? ? HKEY_CURRENT_USER\Envirment ? ? 用戶可以對這些項目進(jìn)行增加、刪除或修改,方法是選定控制面板的S y s t e m小應(yīng)用程序,單擊A d v a n c e d選項卡,再單擊Environment Va r i a b l e s按鈕,打開圖4 - 2所示的對話框:
? ? 只有擁有管理員權(quán)限的用戶才能修改系統(tǒng)變量列表中的變量。
????應(yīng)用程序也可以使用各種注冊表函數(shù)來修改這些注冊表項目。但是,若要使這些
修改在所有應(yīng)用程序中生效,用戶必須退出系統(tǒng),然后再次登錄。有些應(yīng)用程序,如
E x p l o r e r、 Task Manager和 Control Panel等 , 在 它 們 的 主 窗 口 收 到 W M _
S E T T I N G C H A N G E消息時,用新注冊表項目來更新它們的環(huán)境塊。例如,如果要更新
注冊表項目,并且想讓有關(guān)的應(yīng)用程序更新它們的環(huán)境塊,可以調(diào)用下面的代碼:
? ? SendMessage(HWND_BROADCAST ,WM_SETTINGCHANGE ,0 ,(LPARAM)TEXT(“Environment”));
????通常,子進(jìn)程可以繼承一組與父進(jìn)程相同的環(huán)境變量。但是,父進(jìn)程能夠控制子進(jìn)程繼承什么環(huán)境變量,后面介紹C r e a t e P r o c e s s函數(shù)時就會看到這個情況。所謂繼承,指的是子進(jìn)程獲得它自己的父進(jìn)程的環(huán)境塊拷貝,子進(jìn)程與父進(jìn)程并不共享相同的環(huán)境塊。這意味著子進(jìn)程能夠添加、刪除或修改它的環(huán)境塊中的變量,而這個變化在父進(jìn)程的環(huán)境塊中卻得不到反映。
????應(yīng)用程序通常使用環(huán)境變量來使用戶能夠調(diào)整它的行為特性。用戶創(chuàng)建一個環(huán)境變量并對它進(jìn)行初始化。然后,當(dāng)用戶啟動應(yīng)用程序運行時,該應(yīng)用程序要查看環(huán)境塊,找出該變量。如果找到了變量,它就分析變量的值,調(diào)整自己的行為特性。
????環(huán)境變量存在的問題是,用戶難以設(shè)置或理解這些變量。用戶必須正確地拼寫變量的名字,而且必須知道變量值期望的準(zhǔn)確句法。另一方面,大多數(shù)圖形應(yīng)用程序允許用戶使用對話框來調(diào)整應(yīng)用程序的行為特性。這種方法對用戶來說更加友好。
???如果仍然想要使用環(huán)境變量,那么有幾個函數(shù)可供應(yīng)用程序調(diào)用。使用 G e t E n v i r o n m e n tVa r i a b l e函數(shù),就能夠確定某個環(huán)境變量是否存在以及它的值:
?
DWORD GetEnvironmentVariableW(
????_In_opt_ LPCWSTR lpName,
????_Out_writes_to_opt_(nSize, return + 1) LPWSTR lpBuffer,
????_In_ DWORD nSize
????);
TCHAR tcEnviromentVar[MAX_PATH] = {0};
GetEnvironmentVariable(_TEXT("TEMP") ,tcEnviromentVar ,MAX_PATH);
????當(dāng)調(diào)用G e t E n v i r o n m e n t Va r i a b l e時,p s z N a m e指向需要的變量名,p s z Va l u e指向用于存放變量值的緩存,c c h Va l u e用于指明緩存的大小(用字符數(shù)來表示)。該函數(shù)可以返回拷貝到緩存的字符數(shù),如果在環(huán)境中找不到該變量名,也可以返回 0。
許多字符串包含了里面可取代的字符串。例如,我在注冊表中的某個地方找到了下面的字符串:
%USERPROFILE%\My Documents
百分?jǐn)?shù)符號之間的部分表示一個可取代的字符串。在這個例子中,環(huán)境變量的值
USERPROFILE應(yīng)該被放入該字符串中。
由于這種類型的字符串替換是很常用的,因此Wi n d o w s提供了E x p a n d E n v i r o n m e n t S t r i n g s函數(shù):
DWORD ExpandEnvironmentStringsW(
????_In_ LPCWSTR lpSrc,
????_Out_writes_to_opt_(nSize, return) LPWSTR lpDst,
????_In_ DWORD nSize
????);
TCHAR tcFullEnviromentVar[MAX_PATH] = {0};
ExpandEnvironmentStrings(_TEXT("%TEMP%\\A") ,tcFullEnviromentVar ,MAX_PATH);
當(dāng)調(diào)用該函數(shù)時,p s z S r c參數(shù)是包含可替換的環(huán)境變量字符串的這個字符串的地址。p s z D s t參數(shù)是接收已展開字符串的緩存的地址,n S i z e參數(shù)是該緩存的最大值(用字符數(shù)來表示)。
最后,可以使用S e t E n v i r o n m e n t Va r i a b l e函數(shù)來添加變量、刪除變量或者修改變量的值:
BOOL SetEnvironmentVariableW(
????_In_ LPCWSTR lpName,
????_In_opt_ LPCWSTR lpValue
????);
SetEnvironmentVariable(_TEXT("TTT") ,_TEXT("C:"));
該函數(shù)用于將p s z N a m e參數(shù)標(biāo)識的變量設(shè)置為p s z Va l u e參數(shù)標(biāo)識的值。如果帶有指定名字的變量已經(jīng)存在,S e t E n v i r o n m e n t Va r i a b l e就修改該值。如果指定的變量不存在,便添加該變量,如果p s z Va l u e是N U L L,便從環(huán)境塊中刪除該變量。
應(yīng)該始終使用這些函數(shù)來操作進(jìn)程的環(huán)境塊。前面講過,環(huán)境塊中的字符串必須按變量名的字母順序來存放,這樣, S e t E n v i r o n m e n t Va r i a b l e就會很容易地找到它們。 S e t E n v i r o n m e n tVa r i a b l e函數(shù)具有足夠的智能,使環(huán)境變量保持有序排列。
4.1.5 進(jìn)程的親緣性
一般來說,進(jìn)程中的線程可以在主計算機(jī)中的任何一個 C P U上執(zhí)行。但是一個進(jìn)程的線程可能被強(qiáng)制在可用C P U的子集上運行。這稱為進(jìn)程的親緣性,將在第 7章詳細(xì)介紹。子進(jìn)程繼承了父進(jìn)程的親緣性。
4.1.6 進(jìn)程的錯誤模式
????與每個進(jìn)程相關(guān)聯(lián)的是一組標(biāo)志,用于告訴系統(tǒng),進(jìn)程對嚴(yán)重的錯誤應(yīng)該如何作出反映,
這包括磁盤介質(zhì)故障、未處理的異常情況、文件查找失敗和數(shù)據(jù)沒有對齊等。進(jìn)程可以告訴系統(tǒng)如何處理每一種錯誤。方法是調(diào)用S e t E r r o r M o d e函數(shù):
??????????????????????UINT ?SetErrorMode(UINT fuErrorMode);
????f u E r r o r M o d e參數(shù)是下表的任何標(biāo)志按位用O R連接在一起的組合。
?
????默認(rèn)情況下,子進(jìn)程繼承父進(jìn)程的錯誤模式標(biāo)志。換句話說,如果一個進(jìn)程的
S E M _ N O G P FA U LT E R R O R B O X標(biāo)志已經(jīng)打開,并且生成了一個子進(jìn)程,該子進(jìn)程也擁有這個打開的標(biāo)志。但是,子進(jìn)程并沒有得到這一情況的通知,它可能尚未編寫以便處理 G P故障的錯誤。如果G P故障發(fā)生在子進(jìn)程的某個線程中,該子進(jìn)程就會終止運行,而不通知用戶。父進(jìn) 程 可 以 防 止 子 進(jìn) 程 繼 承 它 的 錯 誤 模 式 , 方 法 是 在 調(diào) 用 C r e a t e P r o c e s s 時 設(shè) 定C R E AT E _ D E FA U LT _ E R R O R _ M O D E標(biāo)志(本章后面部分的內(nèi)容將要介紹C r e a t e P r o c e s s函數(shù)) 。
4.1.7 進(jìn)程的當(dāng)前驅(qū)動器和目錄
????當(dāng)不提供全路徑名時,Wi n d o w s的各個函數(shù)就會在當(dāng)前驅(qū)動器的當(dāng)前目錄中查找文件和目錄。例如,如果進(jìn)程中的一個線程調(diào)用 C r e a t e F i l e來打開一個文件(不設(shè)定全路徑名) ,那么系統(tǒng)就會在當(dāng)前驅(qū)動器和目錄中查找該文件。
????系統(tǒng)將在內(nèi)部保持對進(jìn)程的當(dāng)前驅(qū)動器和目錄的跟蹤。 由于該信息是按每個進(jìn)程來維護(hù)的,因此改變當(dāng)前驅(qū)動器或目錄的進(jìn)程中的線程,就可以為該進(jìn)程中的所有線程改變這些信息。
通過調(diào)用下面兩個函數(shù),線程能夠獲得和設(shè)置它的進(jìn)程的當(dāng)前驅(qū)動器和目錄:
?
DWORD GetCurrentDirectoryW(
????_In_ DWORD nBufferLength,
????_Out_writes_to_opt_(nBufferLength, return + 1) LPWSTR lpBuffer
????);
TCHAR tcLocalAppPath[MAX_PATH] = {0};
GetCurrentDirectory(MAX_PATH ,tcLocalAppPath);
BOOL SetCurrentDirectoryW(
????_In_ LPCWSTR lpPathName
????);
SetCurrentDirectory(_TEXT("G:\\inetpub"));
4.1.8 進(jìn)程的當(dāng)前目錄
? ? 系統(tǒng)將對進(jìn)程的當(dāng)前驅(qū)動器和目錄保持跟蹤,但是它不跟蹤每個驅(qū)動器的當(dāng)前目錄。不過,有些操作系統(tǒng)支持對多個驅(qū)動器的當(dāng)前目錄的處理。這種支持是通過進(jìn)程的環(huán)境字符串來提供的。例如,進(jìn)程能夠擁有下面所示的兩個環(huán)境變量:
=C:=C:\Utility\Bin
=D:=D:\Program FIles
? ? 這些變量表示驅(qū)動器C的進(jìn)程的當(dāng)前目錄是\ U t i l i t y \ B i n,并且指明驅(qū)動器D的進(jìn)程的當(dāng)前目錄是\Program Files。
如果調(diào)用一個函數(shù),傳遞一個驅(qū)動器全限定名,以表示一個驅(qū)動器不是當(dāng)前驅(qū)動器,那么系統(tǒng)就會查看進(jìn)程的環(huán)境塊,找出與指定驅(qū)動器名相關(guān)的變量。如果該驅(qū)動器的變量存在,系統(tǒng)將該變量的值用作當(dāng)前驅(qū)動器。如果該變量不存在,系統(tǒng)將假設(shè)指定驅(qū)動器的當(dāng)前目錄是它的根目錄。
例如,如果進(jìn)程的當(dāng)前目錄是 C : \ U t i l i t y | B i n,并且你調(diào)用C r e a t e F i l e來打開D : R e a d M e . T x t,那么系統(tǒng)查看環(huán)境變量 = D。因為= D變量存在,因此系統(tǒng)試圖從 D:\Program Files目錄打開該R e a d M e . T x t文件。如果= D變量不存在,系統(tǒng)將試圖從驅(qū)動器 D的根目錄來打開 R e a d M e . T x t。Wi n d o w s的文件函數(shù)決不會添加或修改驅(qū)動器名的環(huán)境變量,它們只是讀取這些變量。
注意 可以使用C運行期函數(shù)_ c h d i r,而不是使用Wi n d o w s的S e t C u r r e n t D i r e c t o r y函數(shù)來變更當(dāng)前目錄。_ c h d i r函數(shù)從內(nèi)部調(diào)用S e t C u r r e n t D i r e c t o r y,但是_chdir 也能夠添加或修改該環(huán)境變量,這樣,不同驅(qū)動器的當(dāng)前目錄就可以保留。
如果父進(jìn)程創(chuàng)建了一個它想傳遞給子進(jìn)程的環(huán)境塊,子進(jìn)程的環(huán)境塊不會自動繼承父進(jìn)程的當(dāng)前目錄。相反,子進(jìn)程的當(dāng)前目錄將默認(rèn)為每個驅(qū)動器的根目錄。如果想要讓子進(jìn)程繼承父進(jìn)程的當(dāng)前目錄,該父進(jìn)程必須創(chuàng)建這些驅(qū)動器名的環(huán)境變量。并在生成子進(jìn)程前將它們添加給環(huán)境塊。通過調(diào)用G e t F u l l P a t h N a m e,父進(jìn)程可以獲得它的當(dāng)前目錄:
TCHAR szCurDir[MAX_PATH] = {0};
GetFullPathName(_TEXT("C:") ,MAX_PATH ,szCurDir ,NULL);
記住,進(jìn)程的環(huán)境變量必須始終按字母順序來排序。因此驅(qū)動器名的環(huán)境變量通常必須置于環(huán)境塊的開始處。
4.1.9 系統(tǒng)版本
? ? 應(yīng)用程序常常需要確定用戶運行的是哪個 Wi n d o w s版本。例如,通過調(diào)用安全性函數(shù),應(yīng)用程序就能利用它的安全特性。但是這些函數(shù)只有在Windows 2000上才能得到全面的實現(xiàn)。Windows API擁有下面的G e t Ve r s i o n函數(shù):
DWORD GetVersion();
????該函數(shù)已經(jīng)有相當(dāng)長的歷史了。最初它是為 1 6位Wi n d o w s設(shè)計的。它的作用很簡單,在高位字中返回M S - D O S版本號,在低位字中返回Wi n d o w s版本號。對于每個字來說,高位字節(jié)代表主要版本號,低位字節(jié)代表次要版本號。
????但是,編寫該代碼的程序員犯了一個小小的錯誤,函數(shù)的編碼結(jié)果使得 Wi n d o w s的版本號顛倒了,即主要版本號位于低位字節(jié),而次要版本號位于高位字節(jié)。由于許多程序員已經(jīng)開始使用該函數(shù),M i c r o s o f t不得不保持函數(shù)的原樣,并修改了文檔,以說明這個錯誤。
由于圍繞著 G e t Ve r s i o n函數(shù)存在著各種混亂,因此 M i c r o s o f t增加了一個新函數(shù)G e t Ve r s i o n E x :
?
OSVERSIONINFO osvi;
ZeroMemory(&osvi ,sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
GetVersionExW(&osvi);
O S V E R S I O N I N F O E X結(jié)構(gòu)在Windows 2000中是個新結(jié)構(gòu)。Wi n d o w s的其他版本使用較老的O S V E R S I O N I N F O結(jié)構(gòu),它沒有服務(wù)程序包、程序組屏蔽、產(chǎn)品類型和保留成員。
注意,對于系統(tǒng)的版本號中的每個成分來說,該結(jié)構(gòu)擁有不同的成員。這樣做的目的是,
程序員不必提取低位字、高位字、低位字節(jié)和高位字節(jié),因此應(yīng)用程序能夠更加容易地對它們期望的版本號與主機(jī)系統(tǒng)的版本號進(jìn)行比較。下表描述了O S V E R S I O N I N F O E X結(jié)構(gòu)的成員。
?
為了使操作更加容易,Windows 2000提供了一個新的函數(shù),即Ve r i f y Ve r s i o n I n f o,用于對主機(jī)系統(tǒng)的版本與你的應(yīng)用程序需要的版本進(jìn)行比較:
BOOL VerifyVersionInfoW(
????_Inout_ LPOSVERSIONINFOEXW lpVersionInformation,
????_In_ ???DWORD dwTypeMask,
????_In_ ???DWORDLONG dwlConditionMask
????);
若要使用該函數(shù),必須指定一個O S V E R S I O N I N F O E X結(jié)構(gòu),將它的d w O S Ve r s i o n I n f o S i z e成員初始化為該結(jié)構(gòu)的大小,然后對該結(jié)構(gòu)中的其他成員(這些成員對你的應(yīng)用程序來說很重要)進(jìn)行初始化。當(dāng)調(diào)用Ve r i f y Ve r s i o n I n f o時,d w Ty p e M a s k參數(shù)用于指明該結(jié)構(gòu)的哪些成員已經(jīng)進(jìn)行了初始化。 d w Ty p e M a s k參數(shù)是用 O R連接在一起的下列標(biāo)志中的任何一個標(biāo)志:V E R _ M I N O RV E R S I O N,V E R _ M A J O RV E R S I O N,V E R _ B U I L D N U M B E R,V E R _ P L AT F O R M I D,VER_ SERV I C E PA C K M I N O R, V E R _ S E RV I C E PA C K M A J O R, V E R _ S U I T E N A M E,VER_PRODUCT_ TYPE。最后一個參數(shù)d w l C o n d i t i o n M a s k是個6 4位值,用于控制該函數(shù)如何將系統(tǒng)的版本信息與需要的信息進(jìn)行比較。
d w l C o n d i t i o n M a s k描述了如何使用一組復(fù)雜的位組合進(jìn)行的比較。若要創(chuàng)建需要的位組合,可以使用V E R _ S E T _ C O N D I T I O N宏:
VER_SET_CONDITION(
DWORD dwlConditionMask,
? ULONG dwTypeBitMask,
ULONG dwConditionMask)
第一個參數(shù)d w l C o n d i t i o n M a s k用于標(biāo)識一個變量,該變量的位是要操作的那些位。請注意,不必傳遞該變量的地址,因為 V E R _ S E T _ C O N D I T I O N是個宏,不是一個函數(shù)。d w Ty p e B i t M a s k參數(shù)用于指明想要比較的O S V E R S I O N I N F O E X結(jié)構(gòu)中的單個成員。若要比較多個成員,必須多次調(diào)用 V E R _ S E T _ C O N D I T I O N宏,每個成員都要調(diào)用一次。傳遞給Ve r i f y Ve r s i o n I n f o的d w Ty p e M a s k參數(shù)(V E R _ M I N O RV E R S I O N,V E R _ B U I L D N U M B E R等)的標(biāo)志與用于V E R _ S E T _ C O N D I T I O N的d w Ty p e B i t M a s k參數(shù)的標(biāo)志是相同的。
V E R _ S E T _ C O N D I T I O N的最后一個參數(shù)d w C o n d i t i o n M a s k用于指明想如何進(jìn)行比較。它可以是下列值之一:V E R _ E Q U A L,V E R _ G R E AT E R,V E R _ G R E AT E R _ E Q U A L,V E R _ L E S S或V E R _ L E S S _ E Q U A L。請注意,當(dāng)比較V E R _ P R O D U C T _ T Y P E信息時,可以使用這些值。例如,V E R _ N T _ W O R K S TAT I O N小于V E R _ N T _ S E RV E R。但是對于V E R _ S U I T E N A M E信息來說,不能使用這些測試值。相反,必須使用 V E R _ A N D(所有程序組都必須安裝)或 V E R _ O R(至少必須安裝程序組產(chǎn)品中的一個產(chǎn)品) 。
當(dāng)建立一組條件后,可以調(diào)用 Ve r i f y Ve r s i o n I n f o函數(shù),如果調(diào)用成功(如果主機(jī)系統(tǒng)符合應(yīng)用程序的所有要求) ,則返回非零值。如果Ve r i f y Ve r s i o n I n f o返回0,那么主機(jī)系統(tǒng)不符合要求,或者表示對該函數(shù)的調(diào)用不正確。通過調(diào)用 G e t L a s t E r r o r函數(shù),就能確定該函數(shù)為什么返回0。如果G e t L a s t E r r o r返回E R R O R _ O L D _ W I N _ V E R S I O N,那么對該函數(shù)的調(diào)用是正確的,但是系統(tǒng)沒有滿足要求。
下面是如何測試主機(jī)系統(tǒng)是否正是Windows 2000的一個例子:
總結(jié)
以上是生活随笔為你收集整理的Windows核心编程 第四章 进程(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows核心编程 第三章 内核对象
- 下一篇: Intel汇编语言程序设计学习-第六章