yiyuan編程系列4完數(shù)據(jù)庫軟件工程游戲及其它系統(tǒng)windows核心指南_第1頁
yiyuan編程系列4完數(shù)據(jù)庫軟件工程游戲及其它系統(tǒng)windows核心指南_第2頁
yiyuan編程系列4完數(shù)據(jù)庫軟件工程游戲及其它系統(tǒng)windows核心指南_第3頁
yiyuan編程系列4完數(shù)據(jù)庫軟件工程游戲及其它系統(tǒng)windows核心指南_第4頁
yiyuan編程系列4完數(shù)據(jù)庫軟件工程游戲及其它系統(tǒng)windows核心指南_第5頁
已閱讀5頁,還剩67頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

與虛擬內(nèi)存一樣,內(nèi)存映射文件可以用來保留一個地址空間的區(qū)域,并將物理器提交給該區(qū)域。它們之間的差別是,物理器來自一個已經(jīng)位于磁盤上的文件,而不是系統(tǒng)的頁文件。一旦該文件被映射,就可以它,就像整個文件已經(jīng)加載內(nèi)存一樣。系統(tǒng)使用內(nèi)存映射文件,以便加載和執(zhí)行.exe和DLL文件。這可以頁文件空間可以使用內(nèi)存映射文件來磁盤上的數(shù)據(jù)文件。這使你可以不必對文件執(zhí)行I/O操作,可以使用內(nèi)存映射文件,使同一臺計算機上運行的多個進程能夠相互之間共享數(shù)據(jù)。Windows確實提供了其他一些方法,以便在進程之間進行數(shù)據(jù)通信,但是這些方法都是使用內(nèi)存映射文件來實現(xiàn)的,這使得內(nèi)存映射文件成為單個計算機上的多個進程互相進行通信的最有效的方法。內(nèi)存映射的可執(zhí)行文件和DLL系統(tǒng)找出在調(diào)用CreateProcess時設(shè)定的.exe文件。如果找不到這個.exe文件,進程將無法創(chuàng)建,CreateProcess將返回ALSE。系統(tǒng)保留一個足夠大的地址空間區(qū)域,用于存放該.exe文件。該區(qū)域需要的位置在.exe文件本身中設(shè)定。按照默認設(shè)置,.exe文件的址是0x 位Windows2000上運行的64位應(yīng)用程序的地址),但是,可以在創(chuàng)建應(yīng)用程序的.exe文件時重載這個地址,方法是在應(yīng)用程序時使用程序的/BASE選項。系統(tǒng)注意到支持已保留區(qū)域的物理器是在磁盤上的.exe文件中,而不是在系統(tǒng)的頁當.exe文件被映射到進程的地址空間中之后,系統(tǒng)將.exe文件的一個部分,該部分列.exe文件中的代碼要調(diào)用的函數(shù)的DLL文件。然后,系統(tǒng)為每個DLL文件調(diào)用LoadLibrary函數(shù),如果任何一個DLL需要的DLL,那么系統(tǒng)將調(diào)用LoadLibrary函數(shù),以便加載這些DL。每當調(diào)用LoadLibrary來加載一個DL時,系統(tǒng)將執(zhí)行下列操作步驟,它們均類似上面的第4和第5 DLL文件。該區(qū)域需要的位置在DLL文件本身中設(shè)定。按照默認設(shè)置, 的VisualC++建立的DLL文件址是 (這個地址可能不同于在64位Windows2000上運行的64位DLL的地址)但是,你可以在創(chuàng)建DLL文件時重載這個地址,方法是使用程序的/BASE選項。Windows提供的所有標準系統(tǒng)DLL都擁有不同的址,這樣,如果加載到單個地址空間,它們就不會。從DLL中刪除再,這能夠使DLL變得比較小,但是這也意味著該DLL必須加載到它的首選地址中,否則它就根本無法加載。第二,系統(tǒng)必須在DLL中執(zhí)行某些再定位操作。在Windows98中,系統(tǒng)可以在頁面被轉(zhuǎn)入RAM時執(zhí)行再定位操作。在Windows2000中,這些再定系統(tǒng)會注意到支持已保留區(qū)域的物理器位于磁盤上的DLL文件中,而不是在系統(tǒng)的頁文件中。如果由DLL無法加載到它的首選址,Windows2000必須執(zhí)行再定位操作,那么如果由于某個原因系統(tǒng)無法映射.exe和所有必要的DLL文件,那么系統(tǒng)就會向用戶顯示一CreateProcess函數(shù)將向調(diào)用者返回ALSE,調(diào)用者可以調(diào)用GetLastError函數(shù),以便更好地了解為什么無法創(chuàng)建該進程。當所有的.exe和DLL文件都被映射到進程的地址空間之后,系統(tǒng)就可以開始執(zhí)行.exe文件的啟動代碼。當.exe文件被映射后,系統(tǒng)將負責所有的分頁、緩沖和高速緩存的處理。例如,如果.exe文件中的代碼使它跳到一個尚未加載到內(nèi)存的指令地址,那么就會出現(xiàn)一個錯誤。系統(tǒng)能夠發(fā)現(xiàn)這個錯誤,并且自動將這頁代碼從該文件的映像加載到一個RAM頁面。然后,系統(tǒng)將這個RAM頁面映射到進程的地址空間中的相應(yīng)位置,并且讓線程繼續(xù)運行,就像這頁代碼已經(jīng)加載了一樣。當然,這一切是應(yīng)用程序看不見的。當進程中的線程每次試圖尚未加載到RAM的代碼或數(shù)據(jù)時,該進程就會重復(fù)執(zhí)行。當為正在運行的應(yīng)用程序創(chuàng)建新進程時,系統(tǒng)將打開用于標識可執(zhí)行文件映像的文件映射對象的另一個內(nèi)存映射視圖,并創(chuàng)建一個新進程對象和(為主線程創(chuàng)建)一個新線程對象。系統(tǒng)還要將新的進程ID和線程ID賦予這些對象。通過使用內(nèi)存映射文件,同一個應(yīng)用程序的多個正在運行的實例就能夠共享RA中的相同代碼和數(shù)據(jù)。這里有一個小問題需要注意。進程使用的是一個平面地址空間。當編譯和你的程序時所有的代碼和數(shù)據(jù)都被合并在一起,組成一個很大的結(jié)構(gòu)。數(shù)據(jù)與代碼被分開,但僅限于跟在.exe文件中的代碼后面的數(shù)據(jù)而已。圖17-1簡單說明了應(yīng)用程序的代碼和數(shù)據(jù)究竟是如何加載到虛擬內(nèi)存中,然后又被映射到應(yīng)用程序的地址空間中的。的虛擬內(nèi)存頁面映射到第二個應(yīng)用程序的地址空間,如圖17-2所示。 實際上,文件的內(nèi)容被分割為不同的節(jié)。代碼放在一個節(jié)中,全局變量放在另一個節(jié)中。各個節(jié)按照頁面邊界來對齊。通過調(diào)用GetSystemInfo函數(shù),應(yīng)用程序可以確定正在使用的頁面的大小。在.exe或DLL文件中,代碼節(jié)通常位于數(shù)據(jù)數(shù)據(jù)節(jié)的前面。系統(tǒng)運用內(nèi)存管理系統(tǒng)的copy-on-write(寫入時拷貝)特性來防止進行這種改變。每當應(yīng)用程序嘗試將數(shù)據(jù)寫入它的內(nèi)存映射文件時,系統(tǒng)就會抓住這種嘗試,為包含應(yīng)用程序嘗試寫入數(shù)據(jù)的內(nèi)存頁面分配一個新內(nèi)存塊,再拷貝該頁面的內(nèi)容,并允許該應(yīng)用程序?qū)?shù)據(jù)寫入這17-3顯 圖17-1 圖17-2圖17-3應(yīng)用程序的第一個實例嘗試改變數(shù)據(jù)頁面2當應(yīng)用程序被調(diào)試時,將會發(fā)生類似的。比如說,你正在運行一個應(yīng)用程序的多個實例,并且只想調(diào)試其中的一個實例。你調(diào)試程序,在一行源代碼中設(shè)置一個斷點。調(diào)試程遇到了同樣的問題。當調(diào)試程序修改代碼時,它將導(dǎo)致應(yīng)用程序的所有實例在修改后的匯編語言指令運行時激活該調(diào)試程序。為了解決這個問題,系統(tǒng)再次使用copy-on-write內(nèi)存。當系統(tǒng)發(fā)現(xiàn)調(diào)試程序試圖修改代碼時,它就分配一個新內(nèi)存塊,將包含該指令的頁面拷貝到新的內(nèi)存頁面中,并且允許調(diào)試程序修改頁面拷貝中的代碼。Windows98常用copy-on-write屬性保護的那些頁面提交頁文件中的器。這些頁面只是被提交而已,它們并不被。當文件映像中的頁面被時,系統(tǒng)就加載相應(yīng)的頁面。如果該頁面從來沒有被修改,它就可以從內(nèi)存中刪除,并在必要時重新加載。但是,如Windows2000與Windows98之間的行為特性的唯一差別,是在你加載一個模塊的兩個拷貝并且可寫入的數(shù)據(jù)尚未被修改的時候顯示出來的。在這種情況下,在Windows2000下運行的進程能夠共享數(shù)據(jù),而在Wndows8下,每個進程都可以得到它自己的數(shù)據(jù)拷貝。如果只加載模塊的一個拷貝,或者可寫入的數(shù)據(jù)已經(jīng)被修改(這,那么Windows2000indows8的行為特性是完全相同的。全局數(shù)據(jù)和靜態(tài)數(shù)據(jù)不能被同一個.exe或DLL文件的多個映像共享,這是個安全的默認設(shè)置。但是,在某些情況下,讓一個.exe文件的多個映像共個變量的實例是非常有用和方便indows沒有提供任何簡便的方法來確定用戶是否在運行應(yīng)用程序的多個實例。但是,如果能夠讓所有實例共享單個全局變量,那么這個全局變量就能夠反映正在運行的實例的數(shù)量。當用戶啟動應(yīng)用程序的一個實例時,新實例的線程能夠簡單地查看全局變量的值(它已經(jīng)被另一個實例更新)1,那么第二個實例就能夠通知用戶,該應(yīng)用程序只有一個實例可以運行,而第二個實例將終止運行。本節(jié)將介紹法,它允許你共享.exe或DLL文件的所有實例的變量。不過在介紹這個例如,當編譯你的程序時,編譯器會將所有代碼放入一個名叫.text的節(jié)中。該編譯器還將所有表17-1.exe或DLL 使用的VisualStudio的DumpBin實用程序(帶有/Headers開關(guān)),可以查看.exe或DLL映射文件中各個節(jié)的列表。下面選錄的代碼是在一個可執(zhí)行文件上運行DumpBin程序而生表17-2 看到的所有已經(jīng)初始化(initialized)的數(shù)據(jù)變量放入這個新節(jié)中。在上面這個例子中,變量放入Shared節(jié)中。該變量后面的#pragmadataseg()一行告訴編譯器停止將已經(jīng)初始化的變量放入Shared節(jié),并且開始將它們放回到默認數(shù)據(jù)節(jié)中。需要記住的是,編譯器只將已經(jīng)初始化的變量放入新節(jié)中。例如,如果我從前面的代碼段中刪除初始化變量(如下面的代碼所示的VisualC++編譯器提供了一個Allocate說明符,使你可以將 上面的注釋清楚地指明了指定的變量將被放入哪一節(jié)。若要使Allocate的規(guī)確地起作用,那么首先必須創(chuàng)建節(jié)。如果刪除前面這個代碼中的第一行#pragmadata_seg,上面的之所以將變量放入它們自己的節(jié)中,最常見的原因也許是要在.exe或DLL文件的多個映像系統(tǒng)并不為.exe或DLL必須告訴程序,某個節(jié)中的變量是需要加以共享的。若要進行這項操作,可以使用程序令行上的/SECTION開關(guān):在逗號的后面,我們設(shè)定了需要的屬性。用R代表READW代表WEITEE代表EXECUTE,S代表SHARED。上面的開關(guān)用于指明位于Shared節(jié)中的數(shù)據(jù)是可以、寫入和共享的數(shù)據(jù)。如果想要改變多個節(jié)的屬性,必須多次設(shè)定/SECTION開關(guān),也就是為你要改變這一行代碼告訴編譯器將上面的字符串嵌入名字為“.drectve”的節(jié)。當程序?qū)⑺械?obj模塊組合在一起時,程序就要查看每個.obj模塊的“.drectve”節(jié),并且規(guī)定所有的字符串均作為命令行參數(shù)傳遞給該程序。我一直使用這種方法,因為它非常方便。如果將源代碼文件移植到一個新項目中,不必記住在VisualC++的ProjectSettings(項目設(shè)置)框中設(shè)置程序開關(guān)。,并不鼓勵你使用共享節(jié)。第一,用這種方法共享內(nèi)存有可能破壞系統(tǒng)的安全。第二,共享變量意味著一個應(yīng)用程序中的錯誤可能影響另一個應(yīng)用程序的運行,因為它沒有辦法防止某個應(yīng)用程序?qū)?shù)據(jù)隨機寫入一個數(shù)據(jù)塊。假設(shè)你編寫了兩個應(yīng)用程序,每個應(yīng)用程序都要求用戶輸入一個口令。然而你又決定給應(yīng)用程序添加一些特性,使用戶操作起來更加方便些:如果在第二個應(yīng)用程序啟動運行時,用戶正在運行其中的一個應(yīng)用程序,那么第二個應(yīng)用程序就可以查看共享內(nèi)存的內(nèi)容,以便獲得用戶的口令。這樣,如果程序中的某一個已經(jīng)被使用,那么用戶就不必重新輸入他的口令。這聽起來沒有什么問題。畢竟沒有別的應(yīng)用程序而只有你自己的應(yīng)用程序加載了DLL,并且知道到什么地方去查找包含在共享節(jié)中的口令。但是,正在窺視著你的行動,如果他們想要得到你的口令,只需要編寫一段很短的程序,加載到你的公司的DLL文件中,然后共享內(nèi)存塊。當用戶輸令時,的程序就能知道該用戶的口令。精心編制的程序也可能試圖反復(fù)猜測用戶的口令并將它們寫入共享內(nèi)存。一旦該程序猜測到正確的口令,它就能夠?qū)⒏鞣N命令發(fā)送給兩個應(yīng)用程序中的一個。如果有一種辦法只為某些應(yīng)用程序賦予權(quán),以便加載一個特定的DLL,那么這個問題也許是可以解決的。但是目前還不行,因為任何程序都能夠調(diào)用LoadLibrar函數(shù)來顯式加載DLL。17-1列出的AppInst示例應(yīng)用程序(17AppInst.exe”)顯示了應(yīng)用程序如何能夠知道

圖17-4運行AppInst時圖17-5AppInst圖17-6AppInst 這些代碼行用于創(chuàng)建一個稱為Shared的節(jié),該節(jié)擁有、寫入和共享保護屬性。在這個節(jié)中,有一個變量是g_lApplicationInstances該變量是個易失性變量,因此優(yōu)化程序?qū)ξ覀儾黄鸲啻蟮淖饔?。當每個實例的_tWinMan函數(shù)執(zhí)行時,g_lpplcaionnstncs變量就遞增1。在_tWnMn退出之前,該變量將遞減1。我使用nterlokdxhangAdd來改變這個變量,因為多個線程將要當每個實例的框出現(xiàn)時,Dlg_OnInitDialog函數(shù)就被調(diào)用。該函數(shù)將一個窗口消息廣播發(fā)送到所有的窗口(該消息的ID包含在g_aMsgAppInstCountUpdate變量中):系統(tǒng)中的所有窗口將忽略這個窗口消息,但AppInst的各個窗口例外。當我們的各個窗口中的一個接收到該消息時,Dlg_Proc中的代碼將更新該框中的實例數(shù)量,以反映當前的實例數(shù)量(該數(shù)量在g_lApplicationInstances共享變量中進行。17-1AppInst4種方法來實現(xiàn)一第法也是理論上最簡單的方法,它需要分配足夠大的內(nèi)存塊來存放整個文件。該文件被打開,它的內(nèi)容被讀入內(nèi)存塊,然后該文件被關(guān)閉。文件內(nèi)容進入內(nèi)存后,我們就可以對所有字節(jié)的順序進行倒序,方法是將第一個字節(jié)倒騰為最后一個字節(jié),第二個字節(jié)倒騰為倒數(shù)第二個字節(jié),依次類推。這個倒騰操作將一直進行下去直到文件的中間位置。當所有的字節(jié)都已經(jīng)倒騰之后,就可以重新打開該文件,并用內(nèi)存塊的內(nèi)容來改寫它的內(nèi)容。這種方法實現(xiàn)起來非常容易,但是它有兩個缺點。首先,必須分配一個與文件大小相同的內(nèi)存塊。如果文件比較小,那么這沒有什么問題。但是如果文件非常大,比如說有2GB大,那該怎么辦呢?一個32位的系統(tǒng)不允許應(yīng)用程序提交那么大的物理內(nèi)存塊。因此大文件需要使用不同的方法。第二,如果進程在運行過程的中間被中斷,也就是說當?shù)剐蚝蟮淖止?jié)被重新寫入該文件時進程被中斷,那么文件的內(nèi)容就會遭到破壞。防止出現(xiàn)這種情況的最簡單的方法是在對它的內(nèi)容進行倒序之前先制作一個原始文件的拷貝。如果整個進程運行成功,那么可以刪除該文件的拷貝。這種方法需要的磁盤空間。在第二種方法中,你打開現(xiàn)有的文件,并且在磁盤上創(chuàng)建一個長度為08KB8KB的位置,將這最后的8KB讀入緩存,將字節(jié)倒序,再將緩存中的內(nèi)容寫入新創(chuàng)建的文件。這個尋找、讀入、倒序和寫入的操作過程要反復(fù)進行,直到到達原始文件的開頭。如果文件的長度不是8KB的倍數(shù),這種方法實現(xiàn)起來比第法要復(fù)雜一些。它對內(nèi)存的使用效率要高得多,因為它只需要分配一個8KB的緩存塊,但是它存在兩個大問題。首先,它的處理速度比第法要慢,原因是在每個循環(huán)操作過程中,在執(zhí)行讀入操作之前,必須對原始文件進行尋找操作。第二,這種方法可能要使用大量的硬盤空間。如果原始文件是400MB,那么隨著進程的不斷運行,新文件就會增大為400MB800MB間。這比應(yīng)該需要的空間大400MB。由于存在這個缺點,因此引來了下一個方法。8KB的第一個8KB讀入一個緩存,再將文件的第二個8KB讀入另一個緩存。然后進程將兩個緩存的內(nèi)容進行倒序,并將第一個緩存的內(nèi)容寫回文件的結(jié)尾處,將第二個緩存的內(nèi)容寫回同一個文件的開始處。每個迭代操作不斷進行(以8KB為單位,從文件的開始和結(jié)尾處移動文件塊如果文件的長度不是16KB的倍數(shù),并且有兩個8KB的文件塊相,那么就需要進行一些特殊的處理。這種特殊處理比上法中的特殊處理更加復(fù)雜,不過這難不倒經(jīng)驗豐富的編程員。與前面的兩種方法相比,這種方法在節(jié)省硬盤空間方面有它的優(yōu)點。由于所有內(nèi)容都是從同一個文件并寫入同一個文件,因此不需要增加額外的磁盤空間,至于內(nèi)存的使用,這種方法也不錯,它只需要使用16KB的內(nèi)存。當然,這種方法也許是最難實現(xiàn)的方法。與第一種方法一樣,如果進程被中斷,本方導(dǎo)致數(shù)據(jù)文件被破壞。當使用內(nèi)存映射文件對文件內(nèi)容進行倒序時,你打開該文件,然后告訴系統(tǒng)將虛擬地址空間的一個區(qū)域進行倒序。你告訴系統(tǒng)將文件的第一個字節(jié)映射到該保留區(qū)域的第一個字節(jié)。然后可以該虛擬內(nèi)存的區(qū)域,就像它包含了這個文件一樣。實際上,如果在文件的結(jié)尾處有一個單個字節(jié),那么只需要調(diào)用C運行期函數(shù)_strre,就可以對文件中的數(shù)據(jù)進行倒序操作。這種方法的最大優(yōu)點是,系統(tǒng)能夠為你管理所有的文件緩存操作。不必分配任何內(nèi)存,或者將文件數(shù)據(jù)加載到內(nèi)存,也不必將數(shù)據(jù)重新寫入該文件,或者釋放任何內(nèi)存塊。但是,內(nèi)存 CreateFile函數(shù)擁有好幾個參數(shù)。這里只重點介紹前3個參數(shù),即pszFileName,dwDesired你可能會猜到,第一個參數(shù)pszFileName用于指明要創(chuàng)建或打開的文件的名字(包括一個選項路徑。第二個參數(shù)dwDesiredAccess于定何文容可表17-3所列的4 GENERIC_READ 當創(chuàng)建或打開一個文件,將它作為一個內(nèi)存映射文件來使用時,請選定最有意義的一個或多個標志,以說明你打算如何文件的數(shù)據(jù)。對內(nèi)存映射文件來說,必須打開用于只讀或讀寫的文件,因此,可以分別設(shè)定GENERIC_READ或GENERIC_READ|GENERIC_WRITE。表17-4dwShareMode FILE_SHARE_READ 如果CreateFile函數(shù)成功地創(chuàng)建或打開指定的文件,便返回一個文件內(nèi)核對象的句柄,否則返回INVAID_HANDLVAUE。注意能夠返回句柄的大多數(shù)Windows函數(shù)如果運行失敗,那么就會返回NULL調(diào)用CreateFile函數(shù),就可以將文件映像的物理器的位置告訴操作系統(tǒng)。你傳遞的路徑名用于指明支持文件映像的物理器在磁盤(或網(wǎng)絡(luò)或光盤)上的確切位置。這時,必須告訴系統(tǒng),文件映射對象需要多少物理器。若要進行這項操作,可以調(diào)用CreateFileMap本章開頭講過,創(chuàng)建內(nèi)存映射文件就像保留一個地址空間區(qū)域然后將物理器提交給該區(qū)域一樣。因為內(nèi)存映射文件的物理器來自磁盤上的一個文件,而不是來自從系統(tǒng)的頁文件中分配的空間。當創(chuàng)建一個文件映射對象時,系統(tǒng)并不為它保留地址空間區(qū)域,也不將文件的器映射到該區(qū)域(下一節(jié)將介紹如何進行這項操作。但是,當系統(tǒng)將器映射到進程的地址空間中去時,系統(tǒng)必須知道應(yīng)該將什么保護屬性賦予物理器的頁面。CreateFileMap函數(shù)的fdwProtect參數(shù)使你能夠設(shè)定這些保護屬性。大多數(shù)情況下,可以設(shè)定表17-中列出的 使用fdwProtect參數(shù)設(shè)定的部分保護屬 當文件映射對象被映射時,可以文件的數(shù)據(jù)。必須已 AGE_WRITECOPY 當文件映射對象被映射時,可以和寫入文件的數(shù)據(jù)。如果寫入數(shù)據(jù),會導(dǎo)致頁面的私有拷貝得以創(chuàng)建。必須已經(jīng)將GENERIC_READ或GENERIC_WRIT傳遞給CreateFileWindows98在Windows98下,可以將AGE_WRITECOPY標志傳遞給CreateFileMap,這將告訴系統(tǒng)從頁文件中提交器。該頁文件器是為數(shù)據(jù)文件的數(shù)據(jù)拷貝保留的,只有修改過的頁面才被寫入頁文件。你對該文件的數(shù)據(jù)所作的任何修改都不會重新填入原始數(shù)據(jù)文件。其最終結(jié)果是,AGE_WRITECOPY標志的作用在Widows200和Widos98除了上面的頁面保護屬性外,還有4個節(jié)保護屬性,你可以用OR將它們連接起來放入節(jié)的第一個保護屬性是SEC_NOCACHE,它告訴系統(tǒng),沒有將文件的任何內(nèi)存映射頁面放入高速緩存。因此,當將數(shù)據(jù)寫入該文件時,系統(tǒng)將更加經(jīng)常地更新磁盤上的文件數(shù)據(jù)。這個標志與AGE_NOCACHE保護屬性標志一樣,是供設(shè)備驅(qū)動程序開發(fā)人員使用的,應(yīng)用程序通常不使用。Windows98Windows98將忽略SEC_NOCACHE節(jié)的第二個保護屬性是SEC_IMAGE(PE)文件映像。當系統(tǒng)將該文件映射到你的進程的地址空間中時,系統(tǒng)要查看文件的內(nèi)容,以確定將哪些保護屬性賦予文件映像的各個頁面。例如,PE文件的代碼節(jié)(.text)通常用PAGE_EXECUTE_READ屬性進行映射,而PE文件的數(shù)據(jù)節(jié)(.data)則通AGE_READWRITE屬性進行映射。如果設(shè)定的屬性是SEC_IMAGE,則告訴系統(tǒng)進行文件映像的映射,并設(shè)置相應(yīng)的頁面保護屬性。Windows98Windows98將忽略SEC_IMAGE最后兩個保護屬性是 和MIT,它們是兩個互斥屬性,當使用存映射數(shù)據(jù)文件時,它們不能使用。這兩個標志將在本章后面介紹。當創(chuàng)建內(nèi)存映射數(shù)據(jù)文件時,不應(yīng)該設(shè)定這些標志中的任何一個標志。CreateFileMap將忽略這些標志。CreateFileMap的另外兩個參數(shù)是dw umSizeHigh和dw umSizeLow,它們是兩個最重要的參數(shù)。CreateFileMap函數(shù)的主要作用是保證文件映射對象能夠得到足夠的物理器。這兩個參數(shù)將告訴系統(tǒng)該文件的最大字節(jié)數(shù)。它需要兩個32位的值,因為indows支的件小以用64位的值來表示。dw umSizeHigh參數(shù)用于設(shè)定較高的32位,而dw umSizeLow參數(shù)則用于設(shè)定較低的32于4GB于4GB的文件來說,dw umSizeHigh的值將始終是0。使用64位的值,意味著indows能夠處理最大為16EB(1018字節(jié))的文件。如果想要創(chuàng)建一個文件映射對象,使它能夠反映文件當前的大小,那么可以為上面兩個參數(shù)傳遞0。如果只打算該文件或者文件而不改變它的大小,那么為這兩個參數(shù)傳遞0。如果打算將數(shù)據(jù)附加給該文件,可以選擇最大的文件大小,以便為你留出一些富裕的空間。如果當前磁盤上的文件包含0字節(jié),那么可以給CreateFileMap函數(shù)的dw umSizeHigh和dw uSizeLow傳遞兩個0。這樣做就可以告訴系統(tǒng),你要的文件映射對象里面的器為0字節(jié)。這是個錯誤,CreateFileMap將返回NULL。indows支持最大為16EB的文件和文件映射對象,這當然很好,但是,怎樣將這樣大的文件映射到32位進程的地址空間(32位地址空間是4GB文件的上限)中去呢?下一節(jié)介紹解決這個問題的辦法。當然,64位進程擁有16EB的地址空間,因此可以進行更大的文件的映射操作,但是,如果文件是個超大規(guī)模的文件,仍然會遇到類似的問題。若要真正理解CreateFile和CreateFileMap兩個函數(shù)是如何運行的,建議你做一個下面的實驗。建立下面的代碼,對它進行編譯,然后在一個調(diào)試程序中運行它。當你一步步執(zhí)行每個語句時,你會跳到一個命令解釋程序,并執(zhí)行C:\上的“dir”命令。當執(zhí)行調(diào)試程序中的每個語句時,請注意中出現(xiàn)的變化。如果調(diào)用CreateFileMap函數(shù),傳遞AGE_READWRIT標志,那么系統(tǒng)將設(shè)法確保磁盤上的相關(guān)據(jù)文件的大至少與dw umSizeHigh和dw umSizeLow參數(shù)中設(shè)定的,CreateFileMap函數(shù)將擴展該文件的大小,使磁盤上的文件變大。這種擴展是必要的,這樣,當以后將該文件作為內(nèi)存映射文件使用時,物理存儲器就已經(jīng)存在了。如果正在用AGE_READONY或AGE_WRITECOPY標志創(chuàng)建該文件映射對象,那么CreateFileMap特定的文件大小不得大于磁盤文件的物理大小。這是因為你無法將任何數(shù)據(jù)附加給該文件。CreateFileMap函數(shù)的最后一個參數(shù)是pszNam。它是個以0結(jié)尾的字符串,用于給該文件映射對象賦予一個名字。該名字用于與其他進程共享文件映射對象(本章后面展示了它的一個例子。第3章詳細介紹了內(nèi)核對象的共享操作。內(nèi)存映射數(shù)據(jù)文件通常并不需要被共享,因此這個參數(shù)通常是NULL。系統(tǒng)創(chuàng)建文件映射對象,并將用于標識該對象的句柄返回該調(diào)用線程。如果系統(tǒng)無法創(chuàng)建文件映射對象,便返回一個NULL句柄值。記住,當CreateFile運行失敗時,它將返回V_HANDLE_VALUE(定義為-1,當CreateFileMap運行失敗時,它返回NULL。請不要當創(chuàng)建了一個文件映射對象后,仍然必須讓系統(tǒng)為文件的數(shù)據(jù)保留一個地址空間區(qū)域,并將文件的數(shù)據(jù)作為映射到該區(qū)域的物理器進行提交??梢酝ㄟ^調(diào)用MapViewOfFile函數(shù)來 表17-6

可 和寫入文件數(shù)據(jù)。CreateFile 拷貝。在Windows2000中,CreateFileMap 在Windows98中,CreateFileMap indows要求所有這些保護屬性一次又一次地重復(fù)設(shè)置,這當然有些奇怪和煩人。我認為這樣做可以使應(yīng)用程序地對數(shù)據(jù)保護屬性進行控制。剩下的3個參數(shù)與保留地址空間區(qū)域及將物理器映射到該區(qū)域有關(guān)。當你將一個文件映射到你的進程的地址空間中時,你不必地映射整個文件。相反,可以只將文件的一小部分映射到地址空間。被映射到進程的地址空間的這部分文件稱為一個視圖,這可以說明MapViewOfFile當將一個文件視圖映射到進程的地址空間中時,必須規(guī)定兩件事情。首先,必須告訴系統(tǒng)數(shù)據(jù)文件中的哪個字節(jié)應(yīng)該作為視圖中的第一個字節(jié)來映射。你可以使用dwFileOffsetHigh和dwFileOffsetLow參數(shù)來進行這項操作。由于indows支持的文件最大可達16EB,因此必須用一個64位的值來設(shè)定這個字節(jié)的位移值。這個 64位值中,較高的32位傳遞給參數(shù)dwFileOffsetHigh,較的32位傳遞給參數(shù)dwFileOffsetLow。注意,文件中的這個位移值必須是系統(tǒng)的分配粒度的倍數(shù)(迄今為止,indow的所有實現(xiàn)代碼的分配粒度均為64KB第14章介紹了如何獲取某個系統(tǒng)的分配粒度。第二,必須告訴系統(tǒng),數(shù)據(jù)文件有多少字節(jié)要映射到地址空間。這與設(shè)定要保留多大的地址空間區(qū)域的情況是相同的。可以使用dwNumberOfBytesoMap參數(shù)來設(shè)定這個值。如果設(shè)定的值是0,那么系統(tǒng)將設(shè)法把從文件中的指定位移開始到整個文件的結(jié)尾的視圖映射到地址空間。Windows98在Windows98中,如果MapViewOfFile無法找到足夠大的區(qū)域來存放整個文件映射對象,那么無論需要的視圖是多大,MapViewOfFile均將返回NULL。Windows2000在Windows2000中,MapViewOfFile只需要為必要的視圖找到足夠大如果在調(diào)用MapViewOfFile函數(shù)時設(shè)定了FILE_MAP_COPY標志,系統(tǒng)就會從系統(tǒng)的頁文件中提交物理器。提交的地址空間數(shù)量由dwNumberOfBytesoMap參數(shù)決定。只要你不進行其他操作,只是從文件的映像視圖中數(shù)據(jù),那么系統(tǒng)將決不會使用頁文件中的這些提交的頁面。但是,如果進程中的任何線程將數(shù)據(jù)寫入文件的映像視圖中的任何內(nèi)存地址,那么系統(tǒng)將從頁文件中抓取已提交頁面中的一個頁面,將原始數(shù)據(jù)頁面拷貝到該頁交換文件中,然后當系統(tǒng)制作原始頁面的拷貝時,系統(tǒng)將把頁面的保護屬性從PAGE_WRITECOPY改為Windows98 前面講過,Windows98必須預(yù)先為內(nèi)存映射文件提交頁文件中的該函數(shù)的唯一的參數(shù)pvBaseAddress用于設(shè)定返回區(qū)域的址。該值必須與調(diào)用MapViewOfFile函數(shù)返回的值相同。必須記住要調(diào)用UnmapViewOfFile函數(shù)。如果沒有調(diào)用這個函數(shù),那么在你的進程終止運行前,保留的區(qū)域就不會被釋放。每當你調(diào)用MapViewOfFile為了提高速度,系統(tǒng)將文件的數(shù)據(jù)頁面進行高速緩存,并且在對文件的映射視圖進行操作時不立即更新文件的磁盤映像。如果需要確保你的更新被寫入磁盤,可以強制系統(tǒng)將修改過的數(shù)據(jù)的一部分或全部重新寫入磁盤映像中,方法是調(diào)用FlushViewOfFil函數(shù):第一個參數(shù)是包含在內(nèi)存映射文件中的視圖的一個字節(jié)的地址。該函數(shù)將你在這里傳遞的地址圓整為一個頁面邊界值。第二個參數(shù)用于指明你想要刷新的字節(jié)數(shù)。系統(tǒng)將把這個數(shù)字向FlushViewOfFile函數(shù)并且不修改任何數(shù)據(jù),那么該函數(shù)只是返回,而不將任何信息寫入磁盤。,F(xiàn)lushViewOfFil能夠保證文件的數(shù)據(jù)已經(jīng)從工作站寫入器。但是FlushViewOfFile不能保證正在共享文件的服務(wù)器已經(jīng)將數(shù)據(jù)寫入磁盤,因為服務(wù)器也許對文件的數(shù)據(jù)進行了高速緩存。若要保證服務(wù)器寫入文件的數(shù)據(jù),每當你為文件創(chuàng)建一個文件映射對象并且映射該文件映射對象的視圖時,應(yīng)該將FILE_FLAGWRITE_THROUGH標志傳遞給CreateFile函數(shù)。如果你使用該標志打開該文件,那么只有當文FlushViewOfFile記住UnmapViewOfFile函數(shù)的一個特殊的特性。如果原先使用FILE_MAP_COPY標志來映射視圖,那么你對文件的數(shù)據(jù)所作的任何修改,實際上是對存放在系統(tǒng)的頁文件中的文件數(shù)據(jù)UnmapViewOfFile函數(shù),該函數(shù)在磁盤文件上就沒有什么可以更新,而只會釋放頁文件中的頁面,從而導(dǎo)致數(shù)據(jù)丟失。如果想保留修改后的數(shù)據(jù),必須采用別的措施。例如,你可以用同一個文件創(chuàng)建另一個文件映射對象(使用AGE_READWRITE,然后使用FILE_MAP_WRITE標志將這個新文件映射對象映射到進程的地址空間。之后,你可以掃描第一個視圖,尋找?guī)в蠥GE_READWRITE保護屬性的頁面。每當你找到一個帶有該屬性的頁面時,可以查看它的內(nèi)容,并且確定是否將修改了的數(shù)據(jù)寫入該文件。如果不想用新數(shù)據(jù)更新該文件,那么繼續(xù)對視圖中的剩余頁面進行掃描,直到視圖的結(jié)尾。但是,如果你確實想要保存修改了的數(shù)據(jù)頁面,那么只需要調(diào)用MoveMemory函數(shù),將數(shù)據(jù)頁面從第一個視圖拷貝到第二個視圖。由于第二個視圖是用AGE_READWRITE保護屬性映射的,因此MoveMemory函數(shù)將更新磁盤上的實際文件內(nèi)容??梢允褂眠@種方法來確定文件的變更并保存你的文件的數(shù)據(jù)。Windows98Windows98不支持copy-on-write(寫入時拷貝)保護屬性,因此,當掃描內(nèi)存映射文件的第一個視圖時,無法測試用PAGE_READWRITE標志做上標記的頁現(xiàn)資源泄漏的問題。當然,當你的進程終止運行時,系統(tǒng)會自動關(guān)閉你的進程已經(jīng)打開但是忘記關(guān)閉的任何對象。但是如果你的進程暫時沒有終止運行,你將會積累許多資源句柄。因此你始終都應(yīng)該編寫清楚而又“正確的”代碼,以便關(guān)閉你已經(jīng)打開的任何對象。若要關(guān)閉文件映射對象和文件對象,只需要兩次調(diào)用CloseHandle函數(shù),每個句柄調(diào)用一次:上面的代碼顯示了對內(nèi)存映射文件進行操作所用的“預(yù)期”方法。但是,它沒有顯示,當你調(diào)用MapViewOfFile時系統(tǒng)對文件對象和文件映射對象的使用計數(shù)的遞增情況。這個副作用是很大的,因為它意味著我們可以將上面的代碼段重新編寫成下面的樣子:當對內(nèi)存映射文件進行操作時,通常要打開文件,創(chuàng)建文件映射對象,然后使用文件映射對象將文件的數(shù)據(jù)視圖映射到進程的地址空間。由于系統(tǒng)遞增了文件對象和文件映射對象的內(nèi)部使用計數(shù),因此可以在你的代碼開始運行時關(guān)閉這些對象,以消除資源泄漏的可能性。172列出的FileRev應(yīng)用程序(“17對ANSI或Unicode文本文件的內(nèi)容進行倒序。光盤上的17- 下。當啟動該程序時 圖17-7運行FileRev時出現(xiàn)的窗FileRev應(yīng)用程序首先允許選定一個文件,然后,當單擊ReverseFileContents(對文件內(nèi)容進行倒序)正確的倒序,對二進制文件不能正確地進行倒序操作。FileRev能夠確定文本文件是ANSI文件這意味著FileRev示例應(yīng)用程序總是認為,當它在Windows98下運行時,它操作的是當單擊ReverseFileContents按鈕時,F(xiàn)ileRev便制作指定文件的一個拷貝,稱為FileRev.dat。它制作該拷貝的目的是,原始文件不會因為內(nèi)容被倒序而變得無法使用。接著,F(xiàn)ileRev調(diào)用FileReverse函數(shù),該函數(shù)負責對文件進行倒序操作。FileReverse則調(diào)用CreateFile函數(shù),打開前面,對文件內(nèi)容進行倒序的最容易的方法是調(diào)用C運行期函數(shù)_strrev。與所有C字符串一樣,字符串的最后一個字符必須是個0結(jié)束符。由于文本文件不以0為結(jié)束符,因此現(xiàn)在已得了文件長,你能通調(diào)用CreateFileMap函數(shù)創(chuàng)建文件映射對象。創(chuàng)建的文件映射對象的長度是dwFileSize加一個寬字符的大?。?字符來說。當文件映射FileRev的地址空間。變量pvFile包含了MapViewOfFile函數(shù)的返回值,并指向文本文件的第一個字節(jié)。在文本文件中,每一行的結(jié)尾都是一個回車符‘\r)后隨一個換行符‘\。但是,當調(diào)用_strrev對文件進行倒序時,這些字符也會被倒序。如果將已經(jīng)倒序的文本文件加載到文本\n\r”字符都必須重新改為它的原始順序。這個倒序操作是由下面的循環(huán)代碼進行的:在文件被倒序后,F(xiàn)ileRev便進行清除操作,撤消文件映射對象的視圖映象,關(guān)閉所有的FileRev必須刪除附加給文件結(jié)尾處的0字符(記住_strrev并不對結(jié)尾的0字符進行倒序。如果沒有刪除0字符,那么倒序的文件將會多出一個字符,如果再次調(diào)用FileRev函數(shù),將無法使文件還原成它的原始樣子。若要刪除文件結(jié)尾處的0字符,必須后退一注意SetEndOfFile函數(shù)必須在撤消視圖的映象并且關(guān)閉文件映射對象之后調(diào)用,否則,該函數(shù)將返回FALSE,GetLastError則返回ERROR_USER_MAPPED_FILE。這個FileRev函數(shù)做的最后一件事情是產(chǎn)生一個Notepad實例,這樣,就可以查看已經(jīng)倒序的文件。圖17-8顯示了在FileRev.cpp文件上運行FileRev圖17-8FileRev17-2FileRev上一節(jié)講過我要告訴你如何將一個16EB的文件映射到一個較小的地址空間中。當然,你是無法做到這一點的。你必須映射一個只包含一小部分文件數(shù)據(jù)的文件視圖。首先映射一個文件的開頭的視圖。當完成對文件的第一個視圖的時,可以取消它的映像,然后映射一個從文件中的一個更深的位移開始的新視圖。必須重復(fù)這一操作,直到了整個文件。這使得大型內(nèi)存映射文件的處理不太方便,但是,幸好大多數(shù)文件都比較小,因此不會出現(xiàn)這個問題。讓我們看一個例子,它使用一個8GB的文件和一個32位的地址空間。下面是一個例程,它這個算法用于映射64KB(分配粒度的大?。┗蚋〉囊晥D。另外,要記住,MapViewOfFile函數(shù)要求文件的位移是分配粒度大小的倍數(shù)。當每個視圖被映射到地址空間時,對0的掃描不斷進行。當每個64KB的文件塊已經(jīng)映射和掃描完畢時,就要通過關(guān)閉文件映射對象來10KB4KB映射到另一個視圖。只要你是映射相同的文件映射對象,系統(tǒng)就會確保映射的視圖數(shù)據(jù)的相關(guān)性。例如,如果你的應(yīng)用程序改變了一個視圖中的文件內(nèi)容,那么所有其他視圖均被更新以反映這個變化。這是因為盡管頁面多次被映射到進程的虛擬地址空間,但是系統(tǒng)只將數(shù)據(jù)放在單個RAM頁面上。如果多個進程映射單個數(shù)據(jù)文件的視圖,那么數(shù)據(jù)仍然是相關(guān)的,因為在數(shù)據(jù)文件中,每個RAM頁面只有一個實例——正是indows允許創(chuàng)建若干個由單個數(shù)據(jù)文件支持的文件映射對象。indows保證這些不同的文件映射對象的視圖具有相關(guān)性。它只能保證單個文件映射對象的多然而,當對文件進行操作時,沒有理由使另一個應(yīng)用程序無法調(diào)用CreateFile函數(shù)以打開用ReadFile和riteFile函數(shù)來該文件的數(shù)據(jù)和將數(shù)據(jù)寫入該文件。當然,每當一個進程調(diào)用這些函數(shù)時,它必須從內(nèi)存緩沖區(qū)文件數(shù)據(jù)或者將文件數(shù)據(jù)寫入內(nèi)存緩沖區(qū)。該內(nèi)存緩沖區(qū)必須是進程自己創(chuàng)建的一個緩沖區(qū),而不是映射文件使用的內(nèi)存緩沖區(qū)。當兩個應(yīng)用程序打開同一個文件時,問題就可能產(chǎn)生:一個進程可以調(diào)用ReadFile函數(shù)來件的一部,并修它數(shù)據(jù),后用riteFile函數(shù)將數(shù)據(jù)重新寫入文件,而第二個進程的文件映射對象卻不知道第一個進程執(zhí)行的這些操作。由于這個原因,當你為將被內(nèi)存映射的文件調(diào)用CreateFile函數(shù)時,最好將dwShareMode的值設(shè)置為0。這樣就可以告訴系統(tǒng),你想要單獨這個文件,而其他進程都不能打開它。只讀文件不存在相關(guān)性問題,因此它們可以作為很好的內(nèi)存映射文件。內(nèi)存映射文件決不應(yīng)該用于共享網(wǎng)絡(luò)上的可寫入文件,因為系統(tǒng)無法保證數(shù)據(jù)視圖的相關(guān)性。如果某個人的計算機更新了文件的內(nèi)容,其他內(nèi)存中含有原始數(shù)據(jù)的計算機將不知道它的信息已經(jīng)被修改。正如你可以使用irtualAlloc函數(shù)來確定對地址空間進行倒序所用的初始地址一樣,你也可以使用MapViewOfFileEx函數(shù)而不是使用MapViewOfFile函數(shù)來確定一個文件被映射到某個特定的地址。請看下面的代碼:該函數(shù)的所有參數(shù)和返回值均與MapViewOfFile函數(shù)相同,唯一的差別是最后一個參數(shù)一樣,你設(shè)定的目標地址應(yīng)該是分配粒度邊界(64KB)的倍數(shù),否則MapViewOfFileEx將返在Windows2000下,如果設(shè)定的地址不是分配粒度的倍數(shù),就會導(dǎo)致函數(shù)運行失敗,同時GetLastError將返回132(ERROR_MAPPED_ALIGNMEN。在Windows98中,該地址將圓)NULL。MapViewOfFileEx并不設(shè)法尋找另一個地址空間來放置該文件。當然,你可以設(shè)定NULL作為pvBaseAddress參數(shù)的值,這時,當你使用內(nèi)存映射文件與其他進程共享數(shù)據(jù)時,你可以使用MapViewOfFileEx定地址上的內(nèi)存映射文件。表是個極好的例子。在表中,每個節(jié)點或元素均包含列表中的另一個元素的內(nèi)存地址。若要遍歷該列表,必須知道第一個元素的地址,然后參考包含下一個元素地址的元素成員。當使用內(nèi)存映射文件時,這可能成為一個問題。如果一個進程建立了內(nèi)存映射文件中的表,然后與另一個進程共享該文件,那么另一個進程就可能將文件映射到它的地址空間中的一個完全不同的位置上。當?shù)诙€進程視圖遍歷該表時,它查看表的第一個元素,檢索下一個元素的內(nèi)存地址,然后設(shè)法下一個元素。然而,第一個節(jié)點中的下一個元素的地址并不是第二個進程需要查找的地址??梢杂脙煞N辦法來解決這個問題。首先,當?shù)诙€進程將包含表的內(nèi)存映射文件映射MapViewOfFileEx函數(shù)而不是調(diào)用MapViewOfFile。當然,這種方法要求第二個進程必須知道第一個進程原先在建立表時將文件映射到了什么地方。當兩個應(yīng)用程序打算互相進行交互操作時(這是非??赡艿?,這就不會出現(xiàn)任何問題,因為地址可以通過硬編碼放入兩個應(yīng)用程序,或者一個進程可以通知另一個進程使用另一種進程間通信的方式,比如將消息發(fā)送到窗口。第二個方法是創(chuàng)建表的進程將下一個節(jié)點所在的地址中的位移存放在每個節(jié)點中。這要求應(yīng)用程序?qū)⒃撐灰铺砑咏o內(nèi)存映射文件的址,以便每個節(jié)點。這種方法并不高明,因為它的運行速度可能比較慢,它會使程序變得更大(因為編譯器要生成附加代碼來執(zhí)行所有的計算操作,的編譯器為使用Windows98 與xBFFFFFFF之間Windows20000 Windows98和Windows2000實現(xiàn)內(nèi)存映射文件的方法是不同的。必須知道這些差別,因在Windows98下,視圖總是映射到0x 在Windows98中,一個進程可以調(diào)用MapViewOfFile信方式將返回的內(nèi)存地址傳遞給另一個進程的線程。一旦該線程收到這個內(nèi)存地址,該線程就可以成功地文件映射對象的同一個視圖。但是,不應(yīng)該這樣做,原因有二。如果第一個進程調(diào)用UnmapViewOfFile函數(shù),地址空間區(qū)域?qū)⒒謴?fù)為空閑狀態(tài),這意味著第二個進程的線程如果嘗試視圖曾經(jīng)位于其中的內(nèi)存,會一次。如果第二個進程內(nèi)存映射對象的視圖,那么第二個進程中的線程應(yīng)該調(diào)用如果第一個進程調(diào)用UnmapViewOfFile函數(shù),那么在第二個進程也調(diào)用UnmapViewOfFile當?shù)诙€進程調(diào)用MapViewOfFile函數(shù)時,返回的地址將與第一個進程返回的地址相同。這樣,第一個進程就沒有必要使用進程間的通信方式將內(nèi)存地址傳送給第二個進程。Windows2000實現(xiàn)內(nèi)存映射文件的方法要比Windows98好,因為Windows2000程的地址空間中的文件數(shù)據(jù)可供之前,該進程必須調(diào)用MapViewOfFile函數(shù)。如果一個進程調(diào)用MapViewOfFile函數(shù),系統(tǒng)將為調(diào)用進程的地址空間中的視圖進行地址空間區(qū)域的倒序操作,這樣,其他進程都將無法看到該視圖。如果另一個進程想要同一個文件映射對象中MapViewOfFile,同時,系統(tǒng)將為第二個進程的地址空間中的視圖進行地址空間區(qū)域的倒序操作。在Windows98中,當文件映射對象的視圖被映射時,系統(tǒng)將為整個文件映射對象保留足夠的地址空間。即使調(diào)用MapViewOfFile函數(shù)時它的參數(shù)指明你想要系統(tǒng)只映射文件映射對象的一小部分,系統(tǒng)也會為它保留足夠的地址空間。這意味著即使你規(guī)定只映射文件映射對象的一個64KB的部分,也不能將一個1GB的文件映射對象映射到一個視圖中。Windows2000的實現(xiàn)代碼在這里同樣存在很大的差別。在上面的代碼段中,兩次調(diào)用MapViewOfFile函數(shù)將導(dǎo)致Windows2000保留兩個不同的地址空間區(qū)域。第一個區(qū)域的大小是文件映射對象的大小,第二個區(qū)域的大小是文件映射對象的大小減去64KB。盡管存在兩個不而來的。在Windows98下,各個視圖具有相關(guān)性,因為它們位于同一個內(nèi)存中。indows總是出色地提供各種機制,使應(yīng)用程序能夠迅速而方便地共享數(shù)據(jù)和信息。這些機制包括 、 、OLE、DDE、窗口消息(尤其是WM_COPYDA、剪貼板、郵箱、管道和套接字等。在indows中,在單個計算機上共享數(shù)據(jù)的最低層機制是內(nèi)存映射文件。不錯,如果互相進行通信的所有進程都在同一臺計算機上的話,上面提到的所有機制均使用內(nèi)存映射文件從事它們的煩瑣工作。如果要求達到較高的性能和較小的開銷,內(nèi)存映射文件是舉手可得的佳制。數(shù)據(jù)共享方法是通過讓兩個或多個進程映射同一個文件映射對象的視圖來實現(xiàn)的,這意味著它們將共享物理器的同一個頁面。因此,當一個進程將數(shù)據(jù)寫入一個共享文件映射對象的視圖時,其他進程可以立即看到它們視圖中的數(shù)據(jù)變更情況。注意,如果多個進程共享單個文件映射對象,那么所有進程必須使用相同的名字來表示該文件映射對象。讓我們觀察一個例子,啟動一個應(yīng)用程序。當一個應(yīng)用程序啟動時,系統(tǒng)調(diào)用CreateFile函數(shù),打開磁盤上的.exe文件。然后系統(tǒng)調(diào)用CreateFileMap函數(shù),創(chuàng)建一個文件映射對象。最后,系統(tǒng)代表新創(chuàng)建的進程調(diào)用MapViewOfFileEx函數(shù)(它帶有SEC_IMAGE標志,這.exe文件就可以映射到進程的地址空間。這里調(diào)用的是MapViewOfFileEx,而不是MapViewOfFile在.exe文件映像中的址中。系統(tǒng)創(chuàng)建該進程的主線程,將該映射視圖的可執(zhí)行代碼的第一個字節(jié)的地址放入線程的指令指針,然后CPU啟動該代碼的運行。如果用戶運行同一個應(yīng)用程序的第二個實例,系統(tǒng)就認為規(guī)定的.exe文件已經(jīng)存在一個文件映射對象,因此不會創(chuàng)建新的文件對象或者文件映射對象。相反,系統(tǒng)將第二次映射該文件的一個視圖,這次是在新創(chuàng)建的進程的地址空間環(huán)境中映射的。系統(tǒng)所做的工作是將相同的文件同時映射到兩個地址空間。顯然,這是對內(nèi)存的更有效的使用,因為兩個進程將共享包含正在執(zhí)行的這部分代碼的物理器的同一個頁面。與所有內(nèi)核對象一樣,可以使用3種方法與多個進程共享對象,這3種方法是句柄繼承性、句柄命名和句柄。關(guān)于這3種方法的詳細說明,參見第3章的內(nèi)容。行時都要創(chuàng)建一些數(shù)據(jù),并且需要將數(shù)據(jù)傳送給其他進程,或者與其他進程共享。如果應(yīng)用程序必須在磁盤驅(qū)動器上創(chuàng)建數(shù)據(jù)文件,并且將數(shù)據(jù)在磁盤上以便對它進行共享,那么這將是非常不方便的。公司認識到了這一點,并且增加了一些功能,以便創(chuàng)建由系統(tǒng)的頁文件支持的內(nèi)存映射文件,而不是由硬盤文件支持的內(nèi)存映射文件。這個方法與創(chuàng)建內(nèi)存映射磁盤文件所用的方法幾乎相同,不同之處是它更加方便。一方面,它不必調(diào)用CreateFile函數(shù),因為你不是要創(chuàng)建或打開一個指定的文件,你只需要像通常那樣調(diào)用CreateFileMap函數(shù),并且傳遞INVALID_HANDLE_VALUE作為hFile參數(shù)。這將告訴系統(tǒng),你不是創(chuàng)建其物理器駐留在磁盤上的文件中的文件映射對象,相反,你想讓系統(tǒng)從它的頁文件中提交物理器。分配的器的數(shù)量由CreateFileMap函數(shù)的dwumSizeHigh和dwumSizeLow兩個當創(chuàng)建了文件映射對象并且將它的一個視圖映射到進程的地址空間之后,就可以像使用任何內(nèi)存域樣使用。果你想與他進程享數(shù)據(jù),調(diào)用CreateFileMap函數(shù),并傳遞一個以0結(jié)尾的字符串作為pszName參數(shù)。然后,想要該器的其他進程就可以調(diào)用CreateFileMap或OpenFileMap函數(shù),并傳遞相同的名字。當進程不再想要文件映射對象時,該進程應(yīng)該調(diào)用CloseHandle函數(shù)。當所有句柄均 如果上面這個對CreateFile函數(shù)的調(diào)用失敗,它將返回INALID_HANDLE_ALUE。但是,編寫這個代碼的程序員沒有測試一下,看文件是否已經(jīng)創(chuàng)建成功。當CreateFileMap函數(shù)被調(diào)用時,在hFile參數(shù)中傳遞了INVALID_HANDLE_ALUE,這使得系統(tǒng)創(chuàng)建一個使用來自頁文件而不是來自指定的磁盤文件器的文件映像。使用內(nèi)存映射文件的任何輔助代碼都能夠正確地運行。但是,當文件映射對象被撤消時,寫入文件映射器(頁文件)的全部數(shù)據(jù)將被系統(tǒng)撤消。這時,程序員就坐在那里絞盡腦汁,不知道問題究竟出在哪里。因此,必須始終檢查CreateFile函數(shù)的返回值,以確定是否出現(xiàn)了錯誤,因為CreateFile運行失敗的原因太多了。 然后單擊CreateMapOfData(創(chuàng)建數(shù)據(jù)映像)按鈕。當進行這項操作時,MMFShare調(diào)用CreateFileMap函數(shù),創(chuàng)建一個由系統(tǒng)的頁文件支持的4KB內(nèi)存映射文件對象,并將該對命名為MMFSharedData。如果MMFShare發(fā)現(xiàn)已經(jīng)存在一個帶有這個名字的文件映射對象,它就顯示一個消息框,告訴你它不能創(chuàng)建該對象。如果MMFShare地創(chuàng)建了該對象,那么它將進一步將文件的視圖映射到進程的地址空間,并將數(shù)據(jù)從編輯控件拷貝到內(nèi)存 圖17-9運行MMFShare時出現(xiàn)的當數(shù)據(jù)被拷貝后,MMFShare就撤消文件的視圖,使CreateMapOfData按鈕不起作用,并激活CloseMapOfData(關(guān)閉數(shù)據(jù)映像)按鈕。這時,命名為MMFSharedData的內(nèi)存映如果這時轉(zhuǎn)入MMFShare的另一個實例,并且單擊該實例的OpenMapAndGet(打開映像并獲取數(shù)據(jù))按鈕,那么MMFShare將設(shè)法通過調(diào)用OpenFileMap函數(shù),尋找一個稱為MMFSharedData的文件映射對象。如果無法找到帶有該名字的對象,MMFShare就會顯MMFShare找到了這個對象,它將把該對象的視圖映射到它的進程的地址空間,將數(shù)據(jù)從內(nèi)存映射文件拷貝到框的編輯控件中,然后撤消它的映像,關(guān)閉文件映射對象。好極了,你已經(jīng)成功地將數(shù)據(jù)從一個進程傳送到另一個進程??蛑械腃loseMapOfData(關(guān)閉數(shù)據(jù)映像)按鈕用于關(guān)閉文件映射對象,它能夠釋放頁文件中的器。如果不存在任何文件映射對象,那么MMFShare的其他實例將無法打開文件映像并從中取出數(shù)據(jù)。另外,如果一個實例已經(jīng)創(chuàng)建了一個內(nèi)存映射文件,那么其他實例均不得創(chuàng)建內(nèi)存映射文件并改寫文件中包含的數(shù)據(jù)。17-3MMFShare在迄今為止介紹的所有內(nèi)存映射文件中,我們發(fā)現(xiàn)系統(tǒng)要求為內(nèi)存映射文件提交的所有存儲器必須是在磁盤上的數(shù)據(jù)文件中或者是在頁文件中。這意味著我們不能根據(jù)我們的喜好來有效地使用器。讓我們回到第15章中介紹電子表格的內(nèi)容上來,比如說,你想要與另一個進程共享整個電子表格。如果我們使用內(nèi)存映射文件,那么必須為整個電子表格提交物理器:如果CELLDATA結(jié)構(gòu)的大小是128字節(jié),那么這個數(shù)組需要6553600(200x256x128)顯然,我們寧愿將電子表格作為一個文件映射對象來共享,而不必預(yù)先提交所有的物理存儲器。CreateFileMap函數(shù)為這種操作提供了法,即可以在fdwProtect參數(shù)中設(shè)定 只有當創(chuàng)建由系統(tǒng)的頁文件支持的文件映射對象時,這些標志才有意義。MIT標志能使retFieMap從系統(tǒng)的頁文件中提交器。如果兩個標志都不設(shè)定,其結(jié)果也當調(diào)用CreateFileMap函數(shù)并傳遞SEC_RESEVE標志時,系統(tǒng)并不從它的頁文件中提交物理器,它只是返回文件映射對象的一個句柄。這時可以調(diào)用MapViewOfFile或MapViewOfFileEx函數(shù),創(chuàng)建該文件映射對象的視圖。MapViewOfFile和MapViewOfFileEx將保留一個地址空間區(qū)域,并且不提交支持該區(qū)域的任何物理器。對保留區(qū)域中的內(nèi)存地址進行的任何嘗試均將導(dǎo)致線程?,F(xiàn)在我們得到的是一個保留的地址空間區(qū)域和用于標識該區(qū)域的文件映射對象的句柄。其他進程可以使用相同的文件映射對象來映射同一個地址空間區(qū)域的視圖。物理器仍然沒有被提交給該區(qū)域。如果其他進程中的線程試圖它們區(qū)域中的視圖的內(nèi)存地址,這些線程將會。下面是令人感的一些事情。若要將物理器提交給共享區(qū)域,線程需要做的操作只15irtualAlloc函數(shù)將物理器提交給內(nèi)存映射視圖區(qū)域,用irtualAlloc函數(shù)將器提交給開始時通過調(diào)用帶有MEM_RESEVE標志的irtualAlloc函數(shù)而保留的區(qū)域一樣。而且,就像你可以提交稀疏地存在irtualAlloc保留的在MapViewOfFile或MapViewOfFileEx保的將用MapViewOfFil或MapViewOfFileEx保留的區(qū)域時,已經(jīng)映射了相同文件映射對象視圖的所有進程這時就能夠成功地 已經(jīng)提交的頁Windows98通常情況下,當給irtualAlloc函數(shù)傳遞的內(nèi)存地址位于0x 0x7FFFFFF,irtualAlloc的運行就會失敗。但是,當將物理器提交給使用SEC_RESEVE標志創(chuàng)建的內(nèi)存映射文件時,必須調(diào)用irtualAlloc函數(shù),傳遞一個位于0x 至xBFFFFFFF之間的內(nèi)存地址。Windows98知道你正在把器提交給一個保留的內(nèi)存映射文件,并且讓這個函數(shù)調(diào)用取得成功。注意在Windows2000下,無法使用Virtual函數(shù)從使用SEC_RESERVE標志保留的內(nèi)存映射文件中釋放器。但是,Windows98允許在這種情況下調(diào)用VirtualN文件系統(tǒng)(NTFS5)提供了對稀疏文件的支持。這是個非常出色的新特性。使用這個器包含在通常的磁盤文件中,而不是在系統(tǒng)的頁文件中。下面是如何使用稀疏文件特性的一個例子。比如,你想要創(chuàng)建一個MMF文件,以便存放記錄的音頻數(shù)據(jù)。當用戶說話時,你想要將數(shù)字音頻數(shù)據(jù)寫入內(nèi)存緩沖區(qū),并且讓該緩沖區(qū)得MMF當然是在你的代碼中實現(xiàn)這個要求的最容易和最有效的方法。問題是你不知道用戶在單擊Stop(停止)按鈕之前講了多長時間。你可能需要一個足5分鐘或5小時的數(shù)據(jù),這兩個時間長度的差別太大了。但是,當使用稀疏MM時,數(shù)據(jù)文件的大小確實無關(guān)緊要。174列出的MMFSparse應(yīng)用程序(“17MMFSparse.exe)顯示了如何創(chuàng)建一個由NTFS5支持的內(nèi)存映射文件。該應(yīng)用程序的源代碼和資源文件位于本書所附光盤上的17-MMFSparse下。當啟動該程序時,便會出現(xiàn)圖當單擊Createa1MB(1024K

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論