天天看點

Windows核心程式設計 第四章 程序(上)第4章 進 程

第4章 進 程

    本章介紹系統如何管理所有正在運作的應用程式。首先講述什麼是程序,以及系統如何建立程序核心對象,以便管理每個程序。然後将說明如何使用相關的核心對象來對程序進行操作。接着,要介紹程序的各種不同的屬性,以及查詢和修改這些屬性所用的若幹個函數。還要講述建立或生成系統中的輔助程序所用的函數。當然,如果不深入說明如何來結束程序的運作,那麼這樣的介紹肯定是不完整的。現在就來介紹程序的有關内容。程序通常被定義為一個正在運作的程式的執行個體,它由兩個部分組成:

• 一個是作業系統用來管理程序的核心對象。核心對象也是系統用來存放關于程序的統計

資訊的地方。

• 另一個是位址空間,它包含所有可執行子產品或 D L L子產品的代碼和資料。它還包含動态記憶體配置設定的空間。如線程堆棧和堆配置設定空間。

    程序是不活潑的。若要使程序完成某項操作,它必須擁有一個在它的環境中運作的線程,該線程負責執行包含在程序的位址空間中的代碼。實際上,單個程序可能包含若幹個線程,所有這些線程都“同時”執行程序位址空間中的代碼。為此,每個線程都有它自己的一組C P U寄存器和它自己的堆棧。每個程序至少擁有一個線程,來執行程序的位址空間中的代碼。如果沒有線程來執行程序的位址空間中的代碼,那麼程序就沒有存在的理由了,系統就将自動撤消該程序和它的位址空間。若要使所有這些線程都能運作,作業系統就要為每個線程安排一定的 C P U時間。它通過以一種循環方式為線程提供時間片(稱為量程) ,造成一種假象,仿佛所有線程都是

同時運作的一樣。圖4 - 1顯示了在單個C P U的計算機上是如何實作這種運作方式的。如果計算機擁有多個 C P U,那麼作業系統就要使用複雜得多的算法來實作 C P U上線程負載的平衡。

    當建立一個程序時,系統會自動建立它的第一個線程,稱為主線程。然後,該線程可以創

建其他的線程,而這些線程又能建立更多的線程。

Windows核心程式設計 第四章 程式(上)第4章 進 程

    Wi n d o w s 2 0 0 0 Micorsoft Windows 2000能夠在擁有多個C P U的計算機上運作。 例如,我用來撰寫本書的計算機就包含兩個處理器。Windows 2000可以在每個C P U上運作不同的線程,這樣,多個線程就真的在同時運作了。Windows 2000的核心能夠在這種類型的系統上進行所有線程的管理和排程。不必在代碼中進行任何特定的設定就能利用多處理器提供的各種優點。

    Windows 98 Windows 98隻能在單處理器計算機上運作。即使計算機配有多個處理器,Wi n d o w s每次隻能安排一個線程運作,而其他的處理器則處于空閑狀态。

4.1 編寫第一個Wi n d o w s應用程式

    Wi n d o w s支援兩種類型的應用程式。一種是基于圖形使用者界面( G U I)的應用程式,另一種是基于控制台使用者界面(C U I)的應用程式。基于G U I的應用程式有一個圖形前端程式。它能建立視窗,擁有菜單,可以通過對話框與使用者打交道,并可使用所有的标準“ Wi n d o w s”元件。Wi n d o w s配備的所有應用程式附件(如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的應用程式。基于控制台的應用程式屬于文本操作的應用程式。它們通常不能用于建立視窗或處理消息,并且它們不需要圖形使用者界面。雖然基于 C U I的應用程式包含在螢幕上的視窗中,但是視窗隻包含文本。指令外殼程式 C M D . E X E(用于Windows 2000)和COMMAND.COM (用于Windows 98)都是典型的基于C U I的應用程式。

    這兩種類型的應用程式之間的界限是非常模糊的。可以建立用于顯示對話框的 C U I應用程式。例如,指令外殼程式可能擁有一個特殊的指令,使它能夠顯示一個圖形對話框,在這個對話框中,可以標明你要執行的指令,而不必記住該外殼程式支援的各個不同的指令。也可以建立一個基于G U I的應用程式,它能将文本字元串輸出到一個控制台視窗。我常常建立用于建立控制台視窗的G U I應用程式,在這個視窗中,我可以檢視應用程式執行時的調試資訊。當然你也可以在應用程式中使用圖形使用者界面,而不是老式的字元界面,因為字元界面使用起來不太友善。

    當使用Microsoft Visual C++來建立應用程式時,這種內建式環境安裝了許多不同的連結程式開關,這樣,連結程式就可以将相應的子系統嵌入産生的可執行程式。用于 C U I應用程式的連結程式開關是 / S U B S Y S T E M : C O N D O L E,而用于 G U I應用程式的連結程式開關是S U B S Y S T E M : W I N D O W S。當使用者運作一個應用程式時,作業系統的加載程式就會檢視可執行圖形程式的标題,并抓取該子系統的值。如果該值指明一個 C U I應用程式,那麼加載程式就會自動保證為該應用程式建立文本控制台視窗。

    如果該值指明這是個G U I應用程式,那麼加載程式不建立控制台視窗,而隻是加載應用程式。一旦應用程式啟動運作,作業系統就不再考慮應用程式擁有什麼類型的使用者界面。Wi n d o w s應用程式必須擁有一個在應用程式啟動運作時調用的進入點函數。可以使用的進入點函數有4個:

Windows核心程式設計 第四章 程式(上)第4章 進 程

    作業系統實際上并不調用你編寫的進入點函數。它調用的是 C / C + +運作期啟動函數。該函數負責對C / C + +運作期庫進行初始化,這樣,就可以調用m a l l o c和f r e e之類的函數。它還能夠確定已經聲明的任何全局對象和靜态C + +對象能夠在代碼執行以前正确地建立。下面說明源代碼中可以實作哪個進入點以及何時使用該進入點(見表4 - 1 )。

Windows核心程式設計 第四章 程式(上)第4章 進 程

    連結程式負責在它連接配接可執行檔案時選擇相應的 C / C + +運作期啟動函數。如果設定了/ S U B S Y S T E M : W I N D O W S連結程式開關,那麼該連結程式期望找到一個 Wi n M a i n或w Wi n m a i n函數。如果這兩個函數都不存在,連結程式便傳回一個“未轉換的外部符号”的錯誤消息。否則,它可以分别選擇Wi n M a i n C RT S t a r t u p函數或w Wi n M a i n C RT S t a r t u p函數。

 同樣,如果設定了/ S U B S Y S T E M : C O N S O L E連結程式開關,那麼該連結程式便期望找到m a i n或w m a i n函數,并且可以分别選擇 m a i n C RT S t a r t u p函數或w m a i n C RT S t a r t u p函數。     同樣,如果m a i n或w m a i n都不存在,那麼連結程式傳回一條“未轉換外部符号”的消息。     但是,人們很少知道這樣一個情況,即可以從應用程式中全部删除 / S U B S Y S T E M連結程式開關。當這樣做的時候,連結程式能夠自動确定應用程式應該連接配接到哪個子系統。當進行連結時,連結程式要檢視代碼中存在 4個函數(Wi n M a i n、w Wi n M a i n、m a i n或w m a i n)中的哪一個。然後确定可執行程式應該是哪一個子系統,并且确定可執行程式中應該嵌入哪個 C / C + +啟動函數。     Wi n d o w s / Visual C++程式設計新手常犯的錯誤之一是,當建立新的應用程式時,不小心選擇了錯誤的應用程式類型。例如,程式設計員可能建立一個新的 Wi n 3 2應用程式項目,但是建立了一個進入點函數m a i n。當建立應用程式時,程式設計員會看到一個連結程式錯誤消息,因為 w i n 3 2應用程式項目設定了/ S U B S Y S T E M : W I N D O W S連結程式開關,但是不存在Wi n M a i n或w Wi n M a i n函數。這時,程式設計員可以有4個選擇:

    • 将m a i n函數改為Wi n M a i n。通常這不是最佳的選擇,因為程式設計員可能想要建立一個控制台應用程式。

    • 用Visual C++建立一個新的Win32 控制台應用程式,并将現有的源代碼添加給新應用程式項目。這個選項冗長而乏味,因為它好像是從頭開始建立應用程式,而且必須删除原始的應用程式檔案。

    • 單擊Project Settings對話框的 L i n k頁籤,将 / S U B S Y S T E M : W I N D O W S開關改為/ S U B S Y S T E M : C O N S O L E。這是解決問題的一種比較容易的方法,很少有人知道他們隻需要進行這項操作就行了。

    • 單擊Project Settings對話框的L i n k頁籤,然後全部删除/ S U B S Y S T E M : W I N D O W S開關。這是我喜歡選擇的方法,因為它提供了最大的靈活性。現在,連接配接程式将根據源代碼中實作的函數進行正确的操作。

Windows核心程式設計 第四章 程式(上)第4章 進 程

    當用Visual C++的Developer Studio建立新Wi n 3 2應用程式或Wi n 3 2控制台應用程式項目時,我不知道為什麼這沒有成為預設設定。

    所有的C / C + +運作期啟動函數的作用基本上都是相同的。它們的差别在于,它們究竟是處理A N S I字元串還是U n i c o d e字元串,以及它們在對C運作期庫進行初始化後它們調用哪個進入點函數。Visual C++配有C運作期庫的源代碼。可以在CR t0.c檔案中找到這4個啟動函數的代碼。現在将啟動函數的功能歸納如下:

    • 檢索指向新程序的完整指令行的指針。

    • 檢索指向新程序的環境變量的指針。

    • 對C / C + +運作期的全局變量進行初始化。如果包含了 S t d L i b . h檔案,代碼就能通路這些變量。表4 - 1列出了這些變量。

    • 對C運作期記憶體單元配置設定函數(m a l l o c和c a l l o c)和其他低層輸入/輸出例程使用的記憶體棧進行初始化。

    • 為所有全局和靜态C + +類對象調用構造函數。當所有這些初始化操作完成後,C / C + +啟動函數就調用應用程式的進入點函數。如果編寫了一個w Wi n M a i n函數,它将以下面的形式被調用:

Windows核心程式設計 第四章 程式(上)第4章 進 程

    當進入點函數傳回時,啟動函數便調用 C運作期的e x i t函數,将傳回值(n M a i n R e t Va l)傳遞給它。E x i t函數負責下面的操作:

    • 調用由_ o n e x i t函數的調用而注冊的任何函數。

    • 為所有全局的和靜态的C + +類對象調用析構函數。

    • 調用作業系統的E x i t P r o c e s s函數,将n M a i n R e t Va l傳遞給它。這使得該作業系統能夠撤消程序并設定它的e x i t代碼。

    表4 - 2顯示了程式能夠使用的C / C + +運作期全局變量。

Windows核心程式設計 第四章 程式(上)第4章 進 程

4.1.1 程序的執行個體句柄

    加載到程序位址空間的每個可執行檔案或 D L L檔案均被賦予一個獨一無二的執行個體句柄。可

執行檔案的執行個體作為( w ) Wi n M a i n的第一個參數h i n s t E x e來傳遞。對于加載資源的函數調用來說,

通常都需要該句柄的值。例如,若要從可執行檔案的映象來加載圖示資源,需要調用下面這個

函數:

Windows核心程式設計 第四章 程式(上)第4章 進 程

    L o a d I c o n的第一個參數用于指明哪個檔案(可執行檔案或D L L檔案)包含你想加載的資源。許多應用程式在全局變量中儲存( w ) Wi n M a i n的h i n s t E x e參數,這樣,它就很容易被所有可執行檔案的代碼通路。

    Platform SDK文檔中說,有些函數需要H M O D U L E類型的一個參數。它的例子是下面所示

的G e t M o d u l e F i l e N a m e函數:

Windows核心程式設計 第四章 程式(上)第4章 進 程

    注意 實際情況說明,H M O D U L E與H I N S TA N C E是完全相同的對象。如果函數的文檔指明需要一個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。之是以存在兩個資料類型,原因是在1 6位Wi n d o w s中,H M O D U L E和H I N S TA N C E用于辨別不同的東西。     ( w ) Wi n M a i n的h i n s t E x e參數的實際值是系統将可執行檔案的映象加載到程序的位址空間時使用的基本位址空間。例如,如果系統打開了可執行檔案并且将它的内容加載到位址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參數的值就是0 x 0 0 4 0 0 0 0 0。     可執行檔案的映像加載到的基位址是由連結程式決定的。不同的連結程式可以使用不同的預設基位址。Visual C++連結程式使用的預設基位址是0 x 0 0 4 0 0 0 0 0,因為這是在運作Wi n d o w s9 8時可執行檔案的映象可以加載到的最低位址。可以改變應用程式加載到的基位址,方法是使用M i c r o s o f t的連結程式中的/ B A S E : a d d r e s s連結程式開關。

    如果你想在Wi n d o w s上加載的可執行檔案的基位址小于0 x 0 0 4 0 0 0 0 0,那麼Windows 98加載程式必須将可執行檔案重新加載到另一個位址。這會增加加載應用程式所需的時間,不過,這樣一來,至少該應用程式能夠運作。如果開發的應用程式将要同時在 Windows 98和Wi n d o w s2 0 0 0上運作,應該確定應用程式的基位址是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函數傳回可執行檔案或D L L檔案加載到程序的位址空間時所用的句柄/基位址:     

Windows核心程式設計 第四章 程式(上)第4章 進 程

    當調用該函數時,你傳遞一個以 0結尾的字元串,用于設定加載到調用程序的位址空間的可執行檔案或 D L L檔案的名字。如果系統找到了指定的可執行檔案或 D L L檔案名,G e t M o d u l e H a n d l e便傳回該可執行檔案或D L L檔案映象加載到的基位址。如果系統沒有找到該檔案,則傳回 N U L L。也可以調用 G e t M o d u l e H a n d l e,為 p s z M o d u l e參數傳遞 N U L L,G e t M o d u l e H a n d l e傳回調用的可執行檔案的基位址。這正是 C運作期啟動代碼調用 ( w ) Wi n M a i n函數時該代碼執行的操作。     請記住G e t M o d u l e H a n d l e函數的兩個重要特性。首先,它隻檢視調用程序的位址空間。如果調用程序不使用常用的對話框函數,那麼調用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可能加載到了其他程序的位址空間。第二,調用G e t M o d u l e H a n d l e并傳遞N U L L值,就會傳回程序的位址空間中可執行檔案的基位址。是以,即使通過包含在D L L中的代碼來調用(N U L L) ,傳回的值也是可執行檔案的基位址,而不是D L L檔案的基位址。

4.1.2 程序的前一個執行個體句柄

    如前所述,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參數。該參數用在1 6位Wi n d o w s中,并且保留了( w ) Wi n M a i n的一個參數,目的僅僅是為了能夠容易地轉用1 6位Wi n d o w s應用程式。決不應該在代碼中引用該參數。由于這個原因,我總是像下面這樣編寫( w ) Wi n M a i n函數:

Windows核心程式設計 第四章 程式(上)第4章 進 程

4.1.3 程序的指令行

    當一個新程序建立時,它要傳遞一個指令行。該指令行幾乎永遠不會是空的,至少用于建立新程序的可執行檔案的名字是指令行上的第一個标記。但是在後面介紹 C r e a t e P r o c e s s函數時我們将會看到,程序能夠接收由單個字元組成的指令行,即字元串結尾處的零。當 C運作期的啟動代碼開始運作的時候,它要檢索程序的指令行,跳過可執行檔案的名字,并将指向指令行其餘部分的指針傳遞給Wi n M a i n的p s z C m d L i n e參數。值得注意的是,p s z C m d L i n e參數總是指向一個A N S I字元串。但是,如果将Wi n M a i n改為w Wi n M a i n,就能夠通路程序的U n i c o d e版本指令行。

    應用程式可以按照它選擇的方法來分析和轉換指令行字元串。實際上可以寫入 p s z C m d L i n e參數指向的記憶體緩存,但是在任何情況下都不應該寫到緩存的外面去。我總是将它視為隻讀緩存。如果我想修改指令行,首先我要将指令行拷貝到應用程式的本地緩存中,然後再修改本地緩存。

    也可以獲得一個指向程序的完整指令行的指針,方法是調用G e t C o m m a n d L i n e函數:

    PTSTR GetCommandLine();     該函數傳回一個指向包含完整指令行的緩存的指針,該指令行包括執行檔案的完整路徑名。

    許多應用程式常常擁有轉換成它的各個标記的指令行。使用全局性 _ _ a rg c(或_ _ w a rg v)變量,應用程式就能通路指令行的各個組成部分。下面這個函數 C o m m a n d L i n e To A rg v W将U n i c o d e字元串分割成它的各個标記:

    PWSTR CommandLineToArgvW(PWSTR pszCmdLine, int * pNumArgs);     正如該函數名的結尾處的 W所暗示的那樣,該函數隻存在于 U n i c o d e版本中(W是英文單詞‘Wi d e’的縮寫) 。第一個參數p s z C m d L i n e指向一個指令行字元串。這通常是較早時調用G e t C o m m a n d L i n e W而傳回的值。P N u m A rg s參數是個整數位址,該整數被設定為指令行中的參數的數目。     C o m m a n d L i n e To A rg v W将位址傳回給一個U n i c o d e字元串指針的數組。C o m m a n e L i n e To A rg v W負責在内部配置設定記憶體。大多數應用程式不釋放該記憶體,它們在程序運作終止時依靠作業系統來釋放記憶體。這是完全可行的。但是如果想要自己來釋放記憶體,正确的方法是像下面這樣調用H e a p F r e e函數:

Windows核心程式設計 第四章 程式(上)第4章 進 程

4.1.4 程序的環境變量

    每個程序都有一個與它相關的環境塊。環境塊是程序的位址空間中配置設定的一個記憶體塊。每

個環境塊都包含一組字元串,其形式如下:

Windows核心程式設計 第四章 程式(上)第4章 進 程

    每個字元串的第一部分是環境變量的名字,後跟一個等号,等号後面是要賦予變量的值。環境塊中的所有字元串都必須按環境變量名的字母順序進行排序。     由于等号用于将變量名與變量的值分開,是以等号不能是變量名的一部分。另外,變量中的空格是有意義的。例如,如果聲明下面兩個變量,然後将 X Y Z的值與A B C的值進行比較,那麼系統将報告稱,這兩個變量是不同的,因為緊靠着等号的前面或後面的任何空格均作為比較時的條件被考慮在内。

XYZ = Windows (Notice the space after the equal sign.) ABC = Windows 例如,如果将下面兩個字元串添加給環境塊,後面帶有空格的環境變量 X Y Z包含H o m e,而沒有空格的環境變量X Y Z則包含Wo r k。

    XYZ = Home(Notice the sapce before the equal sign.)     XYZ = Word     最後,必須将一個0字元置于所有環境變量的結尾處,以表示環境塊的結束。

    Wi n d o w s 9 8 若要為 Windows 98建立一組初始環境變量,必須修改系統的A u t o E x e c . b a t檔案,将一系列S E T行放入該檔案。每個S E T行都必須采用下面的形式:     SET VarName = VarValue     當重新開機系統時,A u t o E x e c . b a t檔案的内容被分析,設定的任何環境變量均可供在Windows 98會話期間啟動的任何程序使用。

    Windows 2000  當使用者登入到Windows 2000中時,系統建立一個外殼程序并将一組環境字元串與它相關聯。通過檢視系統資料庫中的兩個關鍵字,系統可以獲得一組初始環境字元串。

    第一個關鍵字包含一個适用于系統的所有環境變量的清單:

Windows核心程式設計 第四章 程式(上)第4章 進 程

    第二個關鍵字包含适用于目前登入的使用者的所有環境變量的清單:

    HKEY_CURRENT_USER\Envirment     使用者可以對這些項目進行增加、删除或修改,方法是標明控制台的S y s t e m小應用程式,單擊A d v a n c e d頁籤,再單擊Environment Va r i a b l e s按鈕,打開圖4 - 2所示的對話框:

Windows核心程式設計 第四章 程式(上)第4章 進 程

    隻有擁有管理者權限的使用者才能修改系統變量清單中的變量。

    應用程式也可以使用各種系統資料庫函數來修改這些系統資料庫項目。但是,若要使這些

修改在所有應用程式中生效,使用者必須退出系統,然後再次登入。有些應用程式,如

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消息時,用新系統資料庫項目來更新它們的環境塊。例如,如果要更新

系統資料庫項目,并且想讓有關的應用程式更新它們的環境塊,可以調用下面的代碼:

    SendMessage(HWND_BROADCAST ,WM_SETTINGCHANGE ,0 ,(LPARAM)TEXT(“Environment”));

    通常,子程序可以繼承一組與父程序相同的環境變量。但是,父程序能夠控制子程序繼承什麼環境變量,後面介紹C r e a t e P r o c e s s函數時就會看到這個情況。所謂繼承,指的是子程序獲得它自己的父程序的環境塊拷貝,子程序與父程序并不共享相同的環境塊。這意味着子程序能夠添加、删除或修改它的環境塊中的變量,而這個變化在父程序的環境塊中卻得不到反映。

    應用程式通常使用環境變量來使使用者能夠調整它的行為特性。使用者建立一個環境變量并對它進行初始化。然後,當使用者啟動應用程式運作時,該應用程式要檢視環境塊,找出該變量。如果找到了變量,它就分析變量的值,調整自己的行為特性。

    環境變量存在的問題是,使用者難以設定或了解這些變量。使用者必須正确地拼寫變量的名字,而且必須知道變量值期望的準确句法。另一方面,大多數圖形應用程式允許使用者使用對話框來調整應用程式的行為特性。這種方法對使用者來說更加友好。

   如果仍然想要使用環境變量,那麼有幾個函數可供應用程式調用。使用 G e t E n v i r o n m e n tVa r i a b l e函數,就能夠确定某個環境變量是否存在以及它的值:

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);

    當調用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用于指明緩存的大小(用字元數來表示)。該函數可以傳回拷貝到緩存的字元數,如果在環境中找不到該變量名,也可以傳回 0。

許多字元串包含了裡面可取代的字元串。例如,我在系統資料庫中的某個地方找到了下面的字元串:

%USERPROFILE%\My Documents

百分數符号之間的部分表示一個可取代的字元串。在這個例子中,環境變量的值

USERPROFILE應該被放入該字元串中。

由于這種類型的字元串替換是很常用的,是以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函數:

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);

當調用該函數時,p s z S r c參數是包含可替換的環境變量字元串的這個字元串的位址。p s z D s t參數是接收已展開字元串的緩存的位址,n S i z e參數是該緩存的最大值(用字元數來表示)。

最後,可以使用S e t E n v i r o n m e n t Va r i a b l e函數來添加變量、删除變量或者修改變量的值:

BOOL SetEnvironmentVariableW(

    _In_ LPCWSTR lpName,

    _In_opt_ LPCWSTR lpValue

    );

SetEnvironmentVariable(_TEXT("TTT") ,_TEXT("C:"));

該函數用于将p s z N a m e參數辨別的變量設定為p s z Va l u e參數辨別的值。如果帶有指定名字的變量已經存在,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,便從環境塊中删除該變量。

應該始終使用這些函數來操作程序的環境塊。前面講過,環境塊中的字元串必須按變量名的字母順序來存放,這樣, 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函數具有足夠的智能,使環境變量保持有序排列。

4.1.5 程序的親緣性

一般來說,程序中的線程可以在主計算機中的任何一個 C P U上執行。但是一個程序的線程可能被強制在可用C P U的子集上運作。這稱為程序的親緣性,将在第 7章詳細介紹。子程序繼承了父程序的親緣性。

4.1.6 程序的錯誤模式

    與每個程序相關聯的是一組标志,用于告訴系統,程序對嚴重的錯誤應該如何作出反映,

這包括磁盤媒體故障、未處理的異常情況、檔案查找失敗和資料沒有對齊等。程序可以告訴系統如何處理每一種錯誤。方法是調用S e t E r r o r M o d e函數:

                      UINT  SetErrorMode(UINT fuErrorMode);

    f u E r r o r M o d e參數是下表的任何标志按位用O R連接配接在一起的組合。

    預設情況下,子程序繼承父程序的錯誤模式标志。換句話說,如果一個程序的

S E M _ N O G P FA U LT E R R O R B O X标志已經打開,并且生成了一個子程序,該子程序也擁有這個打開的标志。但是,子程序并沒有得到這一情況的通知,它可能尚未編寫以便處理 G P故障的錯誤。如果G P故障發生在子程序的某個線程中,該子程序就會終止運作,而不通知使用者。父進 程 可 以 防 止 子 進 程 繼 承 它 的 錯 誤 模 式 , 方 法 是 在 調 用 C r e a t e P r o c e s s 時 設 定C R E AT E _ D E FA U LT _ E R R O R _ M O D E标志(本章後面部分的内容将要介紹C r e a t e P r o c e s s函數) 。

4.1.7 程序的目前驅動器和目錄

    當不提供全路徑名時,Wi n d o w s的各個函數就會在目前驅動器的目前目錄中查找檔案和目錄。例如,如果程序中的一個線程調用 C r e a t e F i l e來打開一個檔案(不設定全路徑名) ,那麼系統就會在目前驅動器和目錄中查找該檔案。

    系統将在内部保持對程序的目前驅動器和目錄的跟蹤。 由于該資訊是按每個程序來維護的,是以改變目前驅動器或目錄的程序中的線程,就可以為該程序中的所有線程改變這些資訊。

通過調用下面兩個函數,線程能夠獲得和設定它的程序的目前驅動器和目錄:

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 程序的目前目錄

    系統将對程序的目前驅動器和目錄保持跟蹤,但是它不跟蹤每個驅動器的目前目錄。不過,有些作業系統支援對多個驅動器的目前目錄的處理。這種支援是通過程序的環境字元串來提供的。例如,程序能夠擁有下面所示的兩個環境變量:

=C:=C:\Utility\Bin

=D:=D:\Program FIles

    這些變量表示驅動器C的程序的目前目錄是\ U t i l i t y \ B i n,并且指明驅動器D的程序的目前目錄是\Program Files。

如果調用一個函數,傳遞一個驅動器全限定名,以表示一個驅動器不是目前驅動器,那麼系統就會檢視程序的環境塊,找出與指定驅動器名相關的變量。如果該驅動器的變量存在,系統将該變量的值用作目前驅動器。如果該變量不存在,系統将假設指定驅動器的目前目錄是它的根目錄。

例如,如果程序的目前目錄是 C : \ U t i l i t y | B i n,并且你調用C r e a t e F i l e來打開D : R e a d M e . T x t,那麼系統檢視環境變量 = D。因為= D變量存在,是以系統試圖從 D:\Program Files目錄打開該R e a d M e . T x t檔案。如果= D變量不存在,系統将試圖從驅動器 D的根目錄來打開 R e a d M e . T x t。Wi n d o w s的檔案函數決不會添加或修改驅動器名的環境變量,它們隻是讀取這些變量。

注意 可以使用C運作期函數_ 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函數來變更目前目錄。_ c h d i r函數從内部調用S e t C u r r e n t D i r e c t o r y,但是_chdir 也能夠添加或修改該環境變量,這樣,不同驅動器的目前目錄就可以保留。

如果父程序建立了一個它想傳遞給子程序的環境塊,子程序的環境塊不會自動繼承父程序的目前目錄。相反,子程序的目前目錄将預設為每個驅動器的根目錄。如果想要讓子程序繼承父程序的目前目錄,該父程序必須建立這些驅動器名的環境變量。并在生成子程序前将它們添加給環境塊。通過調用G e t F u l l P a t h N a m e,父程序可以獲得它的目前目錄:

TCHAR szCurDir[MAX_PATH] = {0};

GetFullPathName(_TEXT("C:") ,MAX_PATH ,szCurDir ,NULL);

記住,程序的環境變量必須始終按字母順序來排序。是以驅動器名的環境變量通常必須置于環境塊的開始處。

4.1.9 系統版本

    應用程式常常需要确定使用者運作的是哪個 Wi n d o w s版本。例如,通過調用安全性函數,應用程式就能利用它的安全特性。但是這些函數隻有在Windows 2000上才能得到全面的實作。Windows API擁有下面的G e t Ve r s i o n函數:

DWORD GetVersion();

    該函數已經有相當長的曆史了。最初它是為 1 6位Wi n d o w s設計的。它的作用很簡單,在高位字中傳回M S - D O S版本号,在低位字中傳回Wi n d o w s版本号。對于每個字來說,高位位元組代表主要版本号,低位位元組代表次要版本号。

    但是,編寫該代碼的程式員犯了一個小小的錯誤,函數的編碼結果使得 Wi n d o w s的版本号颠倒了,即主要版本号位于低位位元組,而次要版本号位于高位位元組。由于許多程式員已經開始使用該函數,M i c r o s o f t不得不保持函數的原樣,并修改了文檔,以說明這個錯誤。

由于圍繞着 G e t Ve r s i o n函數存在着各種混亂,是以 M i c r o s o f t增加了一個新函數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結構在Windows 2000中是個新結構。Wi n d o w s的其他版本使用較老的O S V E R S I O N I N F O結構,它沒有服務程式包、程式組屏蔽、産品類型和保留成員。

注意,對于系統的版本号中的每個成分來說,該結構擁有不同的成員。這樣做的目的是,

程式員不必提取低位字、高位字、低位位元組和高位位元組,是以應用程式能夠更加容易地對它們期望的版本号與主機系統的版本号進行比較。下表描述了O S V E R S I O N I N F O E X結構的成員。

Windows核心程式設計 第四章 程式(上)第4章 進 程
Windows核心程式設計 第四章 程式(上)第4章 進 程

為了使操作更加容易,Windows 2000提供了一個新的函數,即Ve r i f y Ve r s i o n I n f o,用于對主機系統的版本與你的應用程式需要的版本進行比較:

BOOL VerifyVersionInfoW(

    _Inout_ LPOSVERSIONINFOEXW lpVersionInformation,

    _In_    DWORD dwTypeMask,

    _In_    DWORDLONG dwlConditionMask

    );

若要使用該函數,必須指定一個O S V E R S I O N I N F O E X結構,将它的d w O S Ve r s i o n I n f o S i z e成員初始化為該結構的大小,然後對該結構中的其他成員(這些成員對你的應用程式來說很重要)進行初始化。當調用Ve r i f y Ve r s i o n I n f o時,d w Ty p e M a s k參數用于指明該結構的哪些成員已經進行了初始化。 d w Ty p e M a s k參數是用 O R連接配接在一起的下列标志中的任何一個标志: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。最後一個參數d w l C o n d i t i o n M a s k是個6 4位值,用于控制該函數如何将系統的版本資訊與需要的資訊進行比較。

d w l C o n d i t i o n M a s k描述了如何使用一組複雜的位組合進行的比較。若要建立需要的位組合,可以使用V E R _ S E T _ C O N D I T I O N宏:

VER_SET_CONDITION(

DWORD dwlConditionMask,

  ULONG dwTypeBitMask,

ULONG dwConditionMask)

第一個參數d w l C o n d i t i o n M a s k用于辨別一個變量,該變量的位是要操作的那些位。請注意,不必傳遞該變量的位址,因為 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參數用于指明想要比較的O S V E R S I O N I N F O E X結構中的單個成員。若要比較多個成員,必須多次調用 V E R _ S E T _ C O N D I T I O N宏,每個成員都要調用一次。傳遞給Ve r i f y Ve r s i o n I n f o的d w Ty p e M a s k參數(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等)的标志與用于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參數的标志是相同的。

V E R _ S E T _ C O N D I T I O N的最後一個參數d w C o n d i t i o n M a s k用于指明想如何進行比較。它可以是下列值之一: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。請注意,當比較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(至少必須安裝程式組産品中的一個産品) 。

當建立一組條件後,可以調用 Ve r i f y Ve r s i o n I n f o函數,如果調用成功(如果主機系統符合應用程式的所有要求) ,則傳回非零值。如果Ve r i f y Ve r s i o n I n f o傳回0,那麼主機系統不符合要求,或者表示對該函數的調用不正确。通過調用 G e t L a s t E r r o r函數,就能确定該函數為什麼傳回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,那麼對該函數的調用是正确的,但是系統沒有滿足要求。

下面是如何測試主機系統是否正是Windows 2000的一個例子:

Windows核心程式設計 第四章 程式(上)第4章 進 程

繼續閱讀