版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
與虛擬內(nèi)存一樣,內(nèi)存映射文件可以用來保留一個(gè)地址空間的區(qū)域,并將物理器提交給該區(qū)域。它們之間的差別是,物理器來自一個(gè)已經(jīng)位于磁盤上的文件,而不是系統(tǒng)的頁文件。一旦該文件被映射,就可以它,就像整個(gè)文件已經(jīng)加載內(nèi)存一樣。系統(tǒng)使用內(nèi)存映射文件,以便加載和執(zhí)行.exe和DLL文件。這可以頁文件空間可以使用內(nèi)存映射文件來磁盤上的數(shù)據(jù)文件。這使你可以不必對文件執(zhí)行I/O操作,可以使用內(nèi)存映射文件,使同一臺(tái)計(jì)算機(jī)上運(yùn)行的多個(gè)進(jìn)程能夠相互之間共享數(shù)據(jù)。Windows確實(shí)提供了其他一些方法,以便在進(jìn)程之間進(jìn)行數(shù)據(jù)通信,但是這些方法都是使用內(nèi)存映射文件來實(shí)現(xiàn)的,這使得內(nèi)存映射文件成為單個(gè)計(jì)算機(jī)上的多個(gè)進(jìn)程互相進(jìn)行通信的最有效的方法。內(nèi)存映射的可執(zhí)行文件和DLL系統(tǒng)找出在調(diào)用CreateProcess時(shí)設(shè)定的.exe文件。如果找不到這個(gè).exe文件,進(jìn)程將無法創(chuàng)建,CreateProcess將返回ALSE。系統(tǒng)保留一個(gè)足夠大的地址空間區(qū)域,用于存放該.exe文件。該區(qū)域需要的位置在.exe文件本身中設(shè)定。按照默認(rèn)設(shè)置,.exe文件的址是0x 位Windows2000上運(yùn)行的64位應(yīng)用程序的地址),但是,可以在創(chuàng)建應(yīng)用程序的.exe文件時(shí)重載這個(gè)地址,方法是在應(yīng)用程序時(shí)使用程序的/BASE選項(xiàng)。系統(tǒng)注意到支持已保留區(qū)域的物理器是在磁盤上的.exe文件中,而不是在系統(tǒng)的頁當(dāng).exe文件被映射到進(jìn)程的地址空間中之后,系統(tǒng)將.exe文件的一個(gè)部分,該部分列.exe文件中的代碼要調(diào)用的函數(shù)的DLL文件。然后,系統(tǒng)為每個(gè)DLL文件調(diào)用LoadLibrary函數(shù),如果任何一個(gè)DLL需要的DLL,那么系統(tǒng)將調(diào)用LoadLibrary函數(shù),以便加載這些DL。每當(dāng)調(diào)用LoadLibrary來加載一個(gè)DL時(shí),系統(tǒng)將執(zhí)行下列操作步驟,它們均類似上面的第4和第5 DLL文件。該區(qū)域需要的位置在DLL文件本身中設(shè)定。按照默認(rèn)設(shè)置, 的VisualC++建立的DLL文件址是 (這個(gè)地址可能不同于在64位Windows2000上運(yùn)行的64位DLL的地址)但是,你可以在創(chuàng)建DLL文件時(shí)重載這個(gè)地址,方法是使用程序的/BASE選項(xiàng)。Windows提供的所有標(biāo)準(zhǔn)系統(tǒng)DLL都擁有不同的址,這樣,如果加載到單個(gè)地址空間,它們就不會(huì)。從DLL中刪除再,這能夠使DLL變得比較小,但是這也意味著該DLL必須加載到它的首選地址中,否則它就根本無法加載。第二,系統(tǒng)必須在DLL中執(zhí)行某些再定位操作。在Windows98中,系統(tǒng)可以在頁面被轉(zhuǎn)入RAM時(shí)執(zhí)行再定位操作。在Windows2000中,這些再定系統(tǒng)會(huì)注意到支持已保留區(qū)域的物理器位于磁盤上的DLL文件中,而不是在系統(tǒng)的頁文件中。如果由DLL無法加載到它的首選址,Windows2000必須執(zhí)行再定位操作,那么如果由于某個(gè)原因系統(tǒng)無法映射.exe和所有必要的DLL文件,那么系統(tǒng)就會(huì)向用戶顯示一CreateProcess函數(shù)將向調(diào)用者返回ALSE,調(diào)用者可以調(diào)用GetLastError函數(shù),以便更好地了解為什么無法創(chuàng)建該進(jìn)程。當(dāng)所有的.exe和DLL文件都被映射到進(jìn)程的地址空間之后,系統(tǒng)就可以開始執(zhí)行.exe文件的啟動(dòng)代碼。當(dāng).exe文件被映射后,系統(tǒng)將負(fù)責(zé)所有的分頁、緩沖和高速緩存的處理。例如,如果.exe文件中的代碼使它跳到一個(gè)尚未加載到內(nèi)存的指令地址,那么就會(huì)出現(xiàn)一個(gè)錯(cuò)誤。系統(tǒng)能夠發(fā)現(xiàn)這個(gè)錯(cuò)誤,并且自動(dòng)將這頁代碼從該文件的映像加載到一個(gè)RAM頁面。然后,系統(tǒng)將這個(gè)RAM頁面映射到進(jìn)程的地址空間中的相應(yīng)位置,并且讓線程繼續(xù)運(yùn)行,就像這頁代碼已經(jīng)加載了一樣。當(dāng)然,這一切是應(yīng)用程序看不見的。當(dāng)進(jìn)程中的線程每次試圖尚未加載到RAM的代碼或數(shù)據(jù)時(shí),該進(jìn)程就會(huì)重復(fù)執(zhí)行。當(dāng)為正在運(yùn)行的應(yīng)用程序創(chuàng)建新進(jìn)程時(shí),系統(tǒng)將打開用于標(biāo)識(shí)可執(zhí)行文件映像的文件映射對象的另一個(gè)內(nèi)存映射視圖,并創(chuàng)建一個(gè)新進(jìn)程對象和(為主線程創(chuàng)建)一個(gè)新線程對象。系統(tǒng)還要將新的進(jìn)程ID和線程ID賦予這些對象。通過使用內(nèi)存映射文件,同一個(gè)應(yīng)用程序的多個(gè)正在運(yùn)行的實(shí)例就能夠共享RA中的相同代碼和數(shù)據(jù)。這里有一個(gè)小問題需要注意。進(jìn)程使用的是一個(gè)平面地址空間。當(dāng)編譯和你的程序時(shí)所有的代碼和數(shù)據(jù)都被合并在一起,組成一個(gè)很大的結(jié)構(gòu)。數(shù)據(jù)與代碼被分開,但僅限于跟在.exe文件中的代碼后面的數(shù)據(jù)而已。圖17-1簡單說明了應(yīng)用程序的代碼和數(shù)據(jù)究竟是如何加載到虛擬內(nèi)存中,然后又被映射到應(yīng)用程序的地址空間中的。的虛擬內(nèi)存頁面映射到第二個(gè)應(yīng)用程序的地址空間,如圖17-2所示。 實(shí)際上,文件的內(nèi)容被分割為不同的節(jié)。代碼放在一個(gè)節(jié)中,全局變量放在另一個(gè)節(jié)中。各個(gè)節(jié)按照頁面邊界來對齊。通過調(diào)用GetSystemInfo函數(shù),應(yīng)用程序可以確定正在使用的頁面的大小。在.exe或DLL文件中,代碼節(jié)通常位于數(shù)據(jù)數(shù)據(jù)節(jié)的前面。系統(tǒng)運(yùn)用內(nèi)存管理系統(tǒng)的copy-on-write(寫入時(shí)拷貝)特性來防止進(jìn)行這種改變。每當(dāng)應(yīng)用程序嘗試將數(shù)據(jù)寫入它的內(nèi)存映射文件時(shí),系統(tǒng)就會(huì)抓住這種嘗試,為包含應(yīng)用程序嘗試寫入數(shù)據(jù)的內(nèi)存頁面分配一個(gè)新內(nèi)存塊,再拷貝該頁面的內(nèi)容,并允許該應(yīng)用程序?qū)?shù)據(jù)寫入這17-3顯 圖17-1 圖17-2圖17-3應(yīng)用程序的第一個(gè)實(shí)例嘗試改變數(shù)據(jù)頁面2當(dāng)應(yīng)用程序被調(diào)試時(shí),將會(huì)發(fā)生類似的。比如說,你正在運(yùn)行一個(gè)應(yīng)用程序的多個(gè)實(shí)例,并且只想調(diào)試其中的一個(gè)實(shí)例。你調(diào)試程序,在一行源代碼中設(shè)置一個(gè)斷點(diǎn)。調(diào)試程遇到了同樣的問題。當(dāng)調(diào)試程序修改代碼時(shí),它將導(dǎo)致應(yīng)用程序的所有實(shí)例在修改后的匯編語言指令運(yùn)行時(shí)激活該調(diào)試程序。為了解決這個(gè)問題,系統(tǒng)再次使用copy-on-write內(nèi)存。當(dāng)系統(tǒng)發(fā)現(xiàn)調(diào)試程序試圖修改代碼時(shí),它就分配一個(gè)新內(nèi)存塊,將包含該指令的頁面拷貝到新的內(nèi)存頁面中,并且允許調(diào)試程序修改頁面拷貝中的代碼。Windows98常用copy-on-write屬性保護(hù)的那些頁面提交頁文件中的器。這些頁面只是被提交而已,它們并不被。當(dāng)文件映像中的頁面被時(shí),系統(tǒng)就加載相應(yīng)的頁面。如果該頁面從來沒有被修改,它就可以從內(nèi)存中刪除,并在必要時(shí)重新加載。但是,如Windows2000與Windows98之間的行為特性的唯一差別,是在你加載一個(gè)模塊的兩個(gè)拷貝并且可寫入的數(shù)據(jù)尚未被修改的時(shí)候顯示出來的。在這種情況下,在Windows2000下運(yùn)行的進(jìn)程能夠共享數(shù)據(jù),而在Wndows8下,每個(gè)進(jìn)程都可以得到它自己的數(shù)據(jù)拷貝。如果只加載模塊的一個(gè)拷貝,或者可寫入的數(shù)據(jù)已經(jīng)被修改(這,那么Windows2000indows8的行為特性是完全相同的。全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)不能被同一個(gè).exe或DLL文件的多個(gè)映像共享,這是個(gè)安全的默認(rèn)設(shè)置。但是,在某些情況下,讓一個(gè).exe文件的多個(gè)映像共個(gè)變量的實(shí)例是非常有用和方便indows沒有提供任何簡便的方法來確定用戶是否在運(yùn)行應(yīng)用程序的多個(gè)實(shí)例。但是,如果能夠讓所有實(shí)例共享單個(gè)全局變量,那么這個(gè)全局變量就能夠反映正在運(yùn)行的實(shí)例的數(shù)量。當(dāng)用戶啟動(dòng)應(yīng)用程序的一個(gè)實(shí)例時(shí),新實(shí)例的線程能夠簡單地查看全局變量的值(它已經(jīng)被另一個(gè)實(shí)例更新)1,那么第二個(gè)實(shí)例就能夠通知用戶,該應(yīng)用程序只有一個(gè)實(shí)例可以運(yùn)行,而第二個(gè)實(shí)例將終止運(yùn)行。本節(jié)將介紹法,它允許你共享.exe或DLL文件的所有實(shí)例的變量。不過在介紹這個(gè)例如,當(dāng)編譯你的程序時(shí),編譯器會(huì)將所有代碼放入一個(gè)名叫.text的節(jié)中。該編譯器還將所有表17-1.exe或DLL 使用的VisualStudio的DumpBin實(shí)用程序(帶有/Headers開關(guān)),可以查看.exe或DLL映射文件中各個(gè)節(jié)的列表。下面選錄的代碼是在一個(gè)可執(zhí)行文件上運(yùn)行DumpBin程序而生表17-2 看到的所有已經(jīng)初始化(initialized)的數(shù)據(jù)變量放入這個(gè)新節(jié)中。在上面這個(gè)例子中,變量放入Shared節(jié)中。該變量后面的#pragmadataseg()一行告訴編譯器停止將已經(jīng)初始化的變量放入Shared節(jié),并且開始將它們放回到默認(rèn)數(shù)據(jù)節(jié)中。需要記住的是,編譯器只將已經(jīng)初始化的變量放入新節(jié)中。例如,如果我從前面的代碼段中刪除初始化變量(如下面的代碼所示的VisualC++編譯器提供了一個(gè)Allocate說明符,使你可以將 上面的注釋清楚地指明了指定的變量將被放入哪一節(jié)。若要使Allocate的規(guī)確地起作用,那么首先必須創(chuàng)建節(jié)。如果刪除前面這個(gè)代碼中的第一行#pragmadata_seg,上面的之所以將變量放入它們自己的節(jié)中,最常見的原因也許是要在.exe或DLL文件的多個(gè)映像系統(tǒng)并不為.exe或DLL必須告訴程序,某個(gè)節(jié)中的變量是需要加以共享的。若要進(jìn)行這項(xiàng)操作,可以使用程序令行上的/SECTION開關(guān):在逗號(hào)的后面,我們設(shè)定了需要的屬性。用R代表READW代表WEITEE代表EXECUTE,S代表SHARED。上面的開關(guān)用于指明位于Shared節(jié)中的數(shù)據(jù)是可以、寫入和共享的數(shù)據(jù)。如果想要改變多個(gè)節(jié)的屬性,必須多次設(shè)定/SECTION開關(guān),也就是為你要改變這一行代碼告訴編譯器將上面的字符串嵌入名字為“.drectve”的節(jié)。當(dāng)程序?qū)⑺械?obj模塊組合在一起時(shí),程序就要查看每個(gè).obj模塊的“.drectve”節(jié),并且規(guī)定所有的字符串均作為命令行參數(shù)傳遞給該程序。我一直使用這種方法,因?yàn)樗浅7奖?。如果將源代碼文件移植到一個(gè)新項(xiàng)目中,不必記住在VisualC++的ProjectSettings(項(xiàng)目設(shè)置)框中設(shè)置程序開關(guān)。,并不鼓勵(lì)你使用共享節(jié)。第一,用這種方法共享內(nèi)存有可能破壞系統(tǒng)的安全。第二,共享變量意味著一個(gè)應(yīng)用程序中的錯(cuò)誤可能影響另一個(gè)應(yīng)用程序的運(yùn)行,因?yàn)樗鼪]有辦法防止某個(gè)應(yīng)用程序?qū)?shù)據(jù)隨機(jī)寫入一個(gè)數(shù)據(jù)塊。假設(shè)你編寫了兩個(gè)應(yīng)用程序,每個(gè)應(yīng)用程序都要求用戶輸入一個(gè)口令。然而你又決定給應(yīng)用程序添加一些特性,使用戶操作起來更加方便些:如果在第二個(gè)應(yīng)用程序啟動(dòng)運(yùn)行時(shí),用戶正在運(yùn)行其中的一個(gè)應(yīng)用程序,那么第二個(gè)應(yīng)用程序就可以查看共享內(nèi)存的內(nèi)容,以便獲得用戶的口令。這樣,如果程序中的某一個(gè)已經(jīng)被使用,那么用戶就不必重新輸入他的口令。這聽起來沒有什么問題。畢竟沒有別的應(yīng)用程序而只有你自己的應(yīng)用程序加載了DLL,并且知道到什么地方去查找包含在共享節(jié)中的口令。但是,正在窺視著你的行動(dòng),如果他們想要得到你的口令,只需要編寫一段很短的程序,加載到你的公司的DLL文件中,然后共享內(nèi)存塊。當(dāng)用戶輸令時(shí),的程序就能知道該用戶的口令。精心編制的程序也可能試圖反復(fù)猜測用戶的口令并將它們寫入共享內(nèi)存。一旦該程序猜測到正確的口令,它就能夠?qū)⒏鞣N命令發(fā)送給兩個(gè)應(yīng)用程序中的一個(gè)。如果有一種辦法只為某些應(yīng)用程序賦予權(quán),以便加載一個(gè)特定的DLL,那么這個(gè)問題也許是可以解決的。但是目前還不行,因?yàn)槿魏纬绦蚨寄軌蛘{(diào)用LoadLibrar函數(shù)來顯式加載DLL。17-1列出的AppInst示例應(yīng)用程序(17AppInst.exe”)顯示了應(yīng)用程序如何能夠知道
圖17-4運(yùn)行AppInst時(shí)圖17-5AppInst圖17-6AppInst 這些代碼行用于創(chuàng)建一個(gè)稱為Shared的節(jié),該節(jié)擁有、寫入和共享保護(hù)屬性。在這個(gè)節(jié)中,有一個(gè)變量是g_lApplicationInstances該變量是個(gè)易失性變量,因此優(yōu)化程序?qū)ξ覀儾黄鸲啻蟮淖饔?。?dāng)每個(gè)實(shí)例的_tWinMan函數(shù)執(zhí)行時(shí),g_lpplcaionnstncs變量就遞增1。在_tWnMn退出之前,該變量將遞減1。我使用nterlokdxhangAdd來改變這個(gè)變量,因?yàn)槎鄠€(gè)線程將要當(dāng)每個(gè)實(shí)例的框出現(xiàn)時(shí),Dlg_OnInitDialog函數(shù)就被調(diào)用。該函數(shù)將一個(gè)窗口消息廣播發(fā)送到所有的窗口(該消息的ID包含在g_aMsgAppInstCountUpdate變量中):系統(tǒng)中的所有窗口將忽略這個(gè)窗口消息,但AppInst的各個(gè)窗口例外。當(dāng)我們的各個(gè)窗口中的一個(gè)接收到該消息時(shí),Dlg_Proc中的代碼將更新該框中的實(shí)例數(shù)量,以反映當(dāng)前的實(shí)例數(shù)量(該數(shù)量在g_lApplicationInstances共享變量中進(jìn)行。17-1AppInst4種方法來實(shí)現(xiàn)一第法也是理論上最簡單的方法,它需要分配足夠大的內(nèi)存塊來存放整個(gè)文件。該文件被打開,它的內(nèi)容被讀入內(nèi)存塊,然后該文件被關(guān)閉。文件內(nèi)容進(jìn)入內(nèi)存后,我們就可以對所有字節(jié)的順序進(jìn)行倒序,方法是將第一個(gè)字節(jié)倒騰為最后一個(gè)字節(jié),第二個(gè)字節(jié)倒騰為倒數(shù)第二個(gè)字節(jié),依次類推。這個(gè)倒騰操作將一直進(jìn)行下去直到文件的中間位置。當(dāng)所有的字節(jié)都已經(jīng)倒騰之后,就可以重新打開該文件,并用內(nèi)存塊的內(nèi)容來改寫它的內(nèi)容。這種方法實(shí)現(xiàn)起來非常容易,但是它有兩個(gè)缺點(diǎn)。首先,必須分配一個(gè)與文件大小相同的內(nèi)存塊。如果文件比較小,那么這沒有什么問題。但是如果文件非常大,比如說有2GB大,那該怎么辦呢?一個(gè)32位的系統(tǒng)不允許應(yīng)用程序提交那么大的物理內(nèi)存塊。因此大文件需要使用不同的方法。第二,如果進(jìn)程在運(yùn)行過程的中間被中斷,也就是說當(dāng)?shù)剐蚝蟮淖止?jié)被重新寫入該文件時(shí)進(jìn)程被中斷,那么文件的內(nèi)容就會(huì)遭到破壞。防止出現(xiàn)這種情況的最簡單的方法是在對它的內(nèi)容進(jìn)行倒序之前先制作一個(gè)原始文件的拷貝。如果整個(gè)進(jìn)程運(yùn)行成功,那么可以刪除該文件的拷貝。這種方法需要的磁盤空間。在第二種方法中,你打開現(xiàn)有的文件,并且在磁盤上創(chuàng)建一個(gè)長度為08KB8KB的位置,將這最后的8KB讀入緩存,將字節(jié)倒序,再將緩存中的內(nèi)容寫入新創(chuàng)建的文件。這個(gè)尋找、讀入、倒序和寫入的操作過程要反復(fù)進(jìn)行,直到到達(dá)原始文件的開頭。如果文件的長度不是8KB的倍數(shù),這種方法實(shí)現(xiàn)起來比第法要復(fù)雜一些。它對內(nèi)存的使用效率要高得多,因?yàn)樗恍枰峙湟粋€(gè)8KB的緩存塊,但是它存在兩個(gè)大問題。首先,它的處理速度比第法要慢,原因是在每個(gè)循環(huán)操作過程中,在執(zhí)行讀入操作之前,必須對原始文件進(jìn)行尋找操作。第二,這種方法可能要使用大量的硬盤空間。如果原始文件是400MB,那么隨著進(jìn)程的不斷運(yùn)行,新文件就會(huì)增大為400MB800MB間。這比應(yīng)該需要的空間大400MB。由于存在這個(gè)缺點(diǎn),因此引來了下一個(gè)方法。8KB的第一個(gè)8KB讀入一個(gè)緩存,再將文件的第二個(gè)8KB讀入另一個(gè)緩存。然后進(jìn)程將兩個(gè)緩存的內(nèi)容進(jìn)行倒序,并將第一個(gè)緩存的內(nèi)容寫回文件的結(jié)尾處,將第二個(gè)緩存的內(nèi)容寫回同一個(gè)文件的開始處。每個(gè)迭代操作不斷進(jìn)行(以8KB為單位,從文件的開始和結(jié)尾處移動(dòng)文件塊如果文件的長度不是16KB的倍數(shù),并且有兩個(gè)8KB的文件塊相,那么就需要進(jìn)行一些特殊的處理。這種特殊處理比上法中的特殊處理更加復(fù)雜,不過這難不倒經(jīng)驗(yàn)豐富的編程員。與前面的兩種方法相比,這種方法在節(jié)省硬盤空間方面有它的優(yōu)點(diǎn)。由于所有內(nèi)容都是從同一個(gè)文件并寫入同一個(gè)文件,因此不需要增加額外的磁盤空間,至于內(nèi)存的使用,這種方法也不錯(cuò),它只需要使用16KB的內(nèi)存。當(dāng)然,這種方法也許是最難實(shí)現(xiàn)的方法。與第一種方法一樣,如果進(jìn)程被中斷,本方導(dǎo)致數(shù)據(jù)文件被破壞。當(dāng)使用內(nèi)存映射文件對文件內(nèi)容進(jìn)行倒序時(shí),你打開該文件,然后告訴系統(tǒng)將虛擬地址空間的一個(gè)區(qū)域進(jìn)行倒序。你告訴系統(tǒng)將文件的第一個(gè)字節(jié)映射到該保留區(qū)域的第一個(gè)字節(jié)。然后可以該虛擬內(nèi)存的區(qū)域,就像它包含了這個(gè)文件一樣。實(shí)際上,如果在文件的結(jié)尾處有一個(gè)單個(gè)字節(jié),那么只需要調(diào)用C運(yùn)行期函數(shù)_strre,就可以對文件中的數(shù)據(jù)進(jìn)行倒序操作。這種方法的最大優(yōu)點(diǎn)是,系統(tǒng)能夠?yàn)槟愎芾硭械奈募彺娌僮?。不必分配任何?nèi)存,或者將文件數(shù)據(jù)加載到內(nèi)存,也不必將數(shù)據(jù)重新寫入該文件,或者釋放任何內(nèi)存塊。但是,內(nèi)存 CreateFile函數(shù)擁有好幾個(gè)參數(shù)。這里只重點(diǎn)介紹前3個(gè)參數(shù),即pszFileName,dwDesired你可能會(huì)猜到,第一個(gè)參數(shù)pszFileName用于指明要?jiǎng)?chuàng)建或打開的文件的名字(包括一個(gè)選項(xiàng)路徑。第二個(gè)參數(shù)dwDesiredAccess于定何文容可表17-3所列的4 GENERIC_READ 當(dāng)創(chuàng)建或打開一個(gè)文件,將它作為一個(gè)內(nèi)存映射文件來使用時(shí),請選定最有意義的一個(gè)或多個(gè)標(biāo)志,以說明你打算如何文件的數(shù)據(jù)。對內(nèi)存映射文件來說,必須打開用于只讀或讀寫的文件,因此,可以分別設(shè)定GENERIC_READ或GENERIC_READ|GENERIC_WRITE。表17-4dwShareMode FILE_SHARE_READ 如果CreateFile函數(shù)成功地創(chuàng)建或打開指定的文件,便返回一個(gè)文件內(nèi)核對象的句柄,否則返回INVAID_HANDLVAUE。注意能夠返回句柄的大多數(shù)Windows函數(shù)如果運(yùn)行失敗,那么就會(huì)返回NULL調(diào)用CreateFile函數(shù),就可以將文件映像的物理器的位置告訴操作系統(tǒng)。你傳遞的路徑名用于指明支持文件映像的物理器在磁盤(或網(wǎng)絡(luò)或光盤)上的確切位置。這時(shí),必須告訴系統(tǒng),文件映射對象需要多少物理器。若要進(jìn)行這項(xiàng)操作,可以調(diào)用CreateFileMap本章開頭講過,創(chuàng)建內(nèi)存映射文件就像保留一個(gè)地址空間區(qū)域然后將物理器提交給該區(qū)域一樣。因?yàn)閮?nèi)存映射文件的物理器來自磁盤上的一個(gè)文件,而不是來自從系統(tǒng)的頁文件中分配的空間。當(dāng)創(chuàng)建一個(gè)文件映射對象時(shí),系統(tǒng)并不為它保留地址空間區(qū)域,也不將文件的器映射到該區(qū)域(下一節(jié)將介紹如何進(jìn)行這項(xiàng)操作。但是,當(dāng)系統(tǒng)將器映射到進(jìn)程的地址空間中去時(shí),系統(tǒng)必須知道應(yīng)該將什么保護(hù)屬性賦予物理器的頁面。CreateFileMap函數(shù)的fdwProtect參數(shù)使你能夠設(shè)定這些保護(hù)屬性。大多數(shù)情況下,可以設(shè)定表17-中列出的 使用fdwProtect參數(shù)設(shè)定的部分保護(hù)屬 當(dāng)文件映射對象被映射時(shí),可以文件的數(shù)據(jù)。必須已 AGE_WRITECOPY 當(dāng)文件映射對象被映射時(shí),可以和寫入文件的數(shù)據(jù)。如果寫入數(shù)據(jù),會(huì)導(dǎo)致頁面的私有拷貝得以創(chuàng)建。必須已經(jīng)將GENERIC_READ或GENERIC_WRIT傳遞給CreateFileWindows98在Windows98下,可以將AGE_WRITECOPY標(biāo)志傳遞給CreateFileMap,這將告訴系統(tǒng)從頁文件中提交器。該頁文件器是為數(shù)據(jù)文件的數(shù)據(jù)拷貝保留的,只有修改過的頁面才被寫入頁文件。你對該文件的數(shù)據(jù)所作的任何修改都不會(huì)重新填入原始數(shù)據(jù)文件。其最終結(jié)果是,AGE_WRITECOPY標(biāo)志的作用在Widows200和Widos98除了上面的頁面保護(hù)屬性外,還有4個(gè)節(jié)保護(hù)屬性,你可以用OR將它們連接起來放入節(jié)的第一個(gè)保護(hù)屬性是SEC_NOCACHE,它告訴系統(tǒng),沒有將文件的任何內(nèi)存映射頁面放入高速緩存。因此,當(dāng)將數(shù)據(jù)寫入該文件時(shí),系統(tǒng)將更加經(jīng)常地更新磁盤上的文件數(shù)據(jù)。這個(gè)標(biāo)志與AGE_NOCACHE保護(hù)屬性標(biāo)志一樣,是供設(shè)備驅(qū)動(dòng)程序開發(fā)人員使用的,應(yīng)用程序通常不使用。Windows98Windows98將忽略SEC_NOCACHE節(jié)的第二個(gè)保護(hù)屬性是SEC_IMAGE(PE)文件映像。當(dāng)系統(tǒng)將該文件映射到你的進(jìn)程的地址空間中時(shí),系統(tǒng)要查看文件的內(nèi)容,以確定將哪些保護(hù)屬性賦予文件映像的各個(gè)頁面。例如,PE文件的代碼節(jié)(.text)通常用PAGE_EXECUTE_READ屬性進(jìn)行映射,而PE文件的數(shù)據(jù)節(jié)(.data)則通AGE_READWRITE屬性進(jìn)行映射。如果設(shè)定的屬性是SEC_IMAGE,則告訴系統(tǒng)進(jìn)行文件映像的映射,并設(shè)置相應(yīng)的頁面保護(hù)屬性。Windows98Windows98將忽略SEC_IMAGE最后兩個(gè)保護(hù)屬性是 和MIT,它們是兩個(gè)互斥屬性,當(dāng)使用存映射數(shù)據(jù)文件時(shí),它們不能使用。這兩個(gè)標(biāo)志將在本章后面介紹。當(dāng)創(chuàng)建內(nèi)存映射數(shù)據(jù)文件時(shí),不應(yīng)該設(shè)定這些標(biāo)志中的任何一個(gè)標(biāo)志。CreateFileMap將忽略這些標(biāo)志。CreateFileMap的另外兩個(gè)參數(shù)是dw umSizeHigh和dw umSizeLow,它們是兩個(gè)最重要的參數(shù)。CreateFileMap函數(shù)的主要作用是保證文件映射對象能夠得到足夠的物理器。這兩個(gè)參數(shù)將告訴系統(tǒng)該文件的最大字節(jié)數(shù)。它需要兩個(gè)32位的值,因?yàn)閕ndows支的件小以用64位的值來表示。dw umSizeHigh參數(shù)用于設(shè)定較高的32位,而dw umSizeLow參數(shù)則用于設(shè)定較低的32于4GB于4GB的文件來說,dw umSizeHigh的值將始終是0。使用64位的值,意味著indows能夠處理最大為16EB(1018字節(jié))的文件。如果想要?jiǎng)?chuàng)建一個(gè)文件映射對象,使它能夠反映文件當(dāng)前的大小,那么可以為上面兩個(gè)參數(shù)傳遞0。如果只打算該文件或者文件而不改變它的大小,那么為這兩個(gè)參數(shù)傳遞0。如果打算將數(shù)據(jù)附加給該文件,可以選擇最大的文件大小,以便為你留出一些富裕的空間。如果當(dāng)前磁盤上的文件包含0字節(jié),那么可以給CreateFileMap函數(shù)的dw umSizeHigh和dw uSizeLow傳遞兩個(gè)0。這樣做就可以告訴系統(tǒng),你要的文件映射對象里面的器為0字節(jié)。這是個(gè)錯(cuò)誤,CreateFileMap將返回NULL。indows支持最大為16EB的文件和文件映射對象,這當(dāng)然很好,但是,怎樣將這樣大的文件映射到32位進(jìn)程的地址空間(32位地址空間是4GB文件的上限)中去呢?下一節(jié)介紹解決這個(gè)問題的辦法。當(dāng)然,64位進(jìn)程擁有16EB的地址空間,因此可以進(jìn)行更大的文件的映射操作,但是,如果文件是個(gè)超大規(guī)模的文件,仍然會(huì)遇到類似的問題。若要真正理解CreateFile和CreateFileMap兩個(gè)函數(shù)是如何運(yùn)行的,建議你做一個(gè)下面的實(shí)驗(yàn)。建立下面的代碼,對它進(jìn)行編譯,然后在一個(gè)調(diào)試程序中運(yùn)行它。當(dāng)你一步步執(zhí)行每個(gè)語句時(shí),你會(huì)跳到一個(gè)命令解釋程序,并執(zhí)行C:\上的“dir”命令。當(dāng)執(zhí)行調(diào)試程序中的每個(gè)語句時(shí),請注意中出現(xiàn)的變化。如果調(diào)用CreateFileMap函數(shù),傳遞AGE_READWRIT標(biāo)志,那么系統(tǒng)將設(shè)法確保磁盤上的相關(guān)據(jù)文件的大至少與dw umSizeHigh和dw umSizeLow參數(shù)中設(shè)定的,CreateFileMap函數(shù)將擴(kuò)展該文件的大小,使磁盤上的文件變大。這種擴(kuò)展是必要的,這樣,當(dāng)以后將該文件作為內(nèi)存映射文件使用時(shí),物理存儲(chǔ)器就已經(jīng)存在了。如果正在用AGE_READONY或AGE_WRITECOPY標(biāo)志創(chuàng)建該文件映射對象,那么CreateFileMap特定的文件大小不得大于磁盤文件的物理大小。這是因?yàn)槟銦o法將任何數(shù)據(jù)附加給該文件。CreateFileMap函數(shù)的最后一個(gè)參數(shù)是pszNam。它是個(gè)以0結(jié)尾的字符串,用于給該文件映射對象賦予一個(gè)名字。該名字用于與其他進(jìn)程共享文件映射對象(本章后面展示了它的一個(gè)例子。第3章詳細(xì)介紹了內(nèi)核對象的共享操作。內(nèi)存映射數(shù)據(jù)文件通常并不需要被共享,因此這個(gè)參數(shù)通常是NULL。系統(tǒng)創(chuàng)建文件映射對象,并將用于標(biāo)識(shí)該對象的句柄返回該調(diào)用線程。如果系統(tǒng)無法創(chuàng)建文件映射對象,便返回一個(gè)NULL句柄值。記住,當(dāng)CreateFile運(yùn)行失敗時(shí),它將返回V_HANDLE_VALUE(定義為-1,當(dāng)CreateFileMap運(yùn)行失敗時(shí),它返回NULL。請不要當(dāng)創(chuàng)建了一個(gè)文件映射對象后,仍然必須讓系統(tǒng)為文件的數(shù)據(jù)保留一個(gè)地址空間區(qū)域,并將文件的數(shù)據(jù)作為映射到該區(qū)域的物理器進(jìn)行提交??梢酝ㄟ^調(diào)用MapViewOfFile函數(shù)來 表17-6
可 和寫入文件數(shù)據(jù)。CreateFile 拷貝。在Windows2000中,CreateFileMap 在Windows98中,CreateFileMap indows要求所有這些保護(hù)屬性一次又一次地重復(fù)設(shè)置,這當(dāng)然有些奇怪和煩人。我認(rèn)為這樣做可以使應(yīng)用程序地對數(shù)據(jù)保護(hù)屬性進(jìn)行控制。剩下的3個(gè)參數(shù)與保留地址空間區(qū)域及將物理器映射到該區(qū)域有關(guān)。當(dāng)你將一個(gè)文件映射到你的進(jìn)程的地址空間中時(shí),你不必地映射整個(gè)文件。相反,可以只將文件的一小部分映射到地址空間。被映射到進(jìn)程的地址空間的這部分文件稱為一個(gè)視圖,這可以說明MapViewOfFile當(dāng)將一個(gè)文件視圖映射到進(jìn)程的地址空間中時(shí),必須規(guī)定兩件事情。首先,必須告訴系統(tǒng)數(shù)據(jù)文件中的哪個(gè)字節(jié)應(yīng)該作為視圖中的第一個(gè)字節(jié)來映射。你可以使用dwFileOffsetHigh和dwFileOffsetLow參數(shù)來進(jìn)行這項(xiàng)操作。由于indows支持的文件最大可達(dá)16EB,因此必須用一個(gè)64位的值來設(shè)定這個(gè)字節(jié)的位移值。這個(gè) 64位值中,較高的32位傳遞給參數(shù)dwFileOffsetHigh,較的32位傳遞給參數(shù)dwFileOffsetLow。注意,文件中的這個(gè)位移值必須是系統(tǒng)的分配粒度的倍數(shù)(迄今為止,indow的所有實(shí)現(xiàn)代碼的分配粒度均為64KB第14章介紹了如何獲取某個(gè)系統(tǒng)的分配粒度。第二,必須告訴系統(tǒng),數(shù)據(jù)文件有多少字節(jié)要映射到地址空間。這與設(shè)定要保留多大的地址空間區(qū)域的情況是相同的。可以使用dwNumberOfBytesoMap參數(shù)來設(shè)定這個(gè)值。如果設(shè)定的值是0,那么系統(tǒng)將設(shè)法把從文件中的指定位移開始到整個(gè)文件的結(jié)尾的視圖映射到地址空間。Windows98在Windows98中,如果MapViewOfFile無法找到足夠大的區(qū)域來存放整個(gè)文件映射對象,那么無論需要的視圖是多大,MapViewOfFile均將返回NULL。Windows2000在Windows2000中,MapViewOfFile只需要為必要的視圖找到足夠大如果在調(diào)用MapViewOfFile函數(shù)時(shí)設(shè)定了FILE_MAP_COPY標(biāo)志,系統(tǒng)就會(huì)從系統(tǒng)的頁文件中提交物理器。提交的地址空間數(shù)量由dwNumberOfBytesoMap參數(shù)決定。只要你不進(jìn)行其他操作,只是從文件的映像視圖中數(shù)據(jù),那么系統(tǒng)將決不會(huì)使用頁文件中的這些提交的頁面。但是,如果進(jìn)程中的任何線程將數(shù)據(jù)寫入文件的映像視圖中的任何內(nèi)存地址,那么系統(tǒng)將從頁文件中抓取已提交頁面中的一個(gè)頁面,將原始數(shù)據(jù)頁面拷貝到該頁交換文件中,然后當(dāng)系統(tǒng)制作原始頁面的拷貝時(shí),系統(tǒng)將把頁面的保護(hù)屬性從PAGE_WRITECOPY改為Windows98 前面講過,Windows98必須預(yù)先為內(nèi)存映射文件提交頁文件中的該函數(shù)的唯一的參數(shù)pvBaseAddress用于設(shè)定返回區(qū)域的址。該值必須與調(diào)用MapViewOfFile函數(shù)返回的值相同。必須記住要調(diào)用UnmapViewOfFile函數(shù)。如果沒有調(diào)用這個(gè)函數(shù),那么在你的進(jìn)程終止運(yùn)行前,保留的區(qū)域就不會(huì)被釋放。每當(dāng)你調(diào)用MapViewOfFile為了提高速度,系統(tǒng)將文件的數(shù)據(jù)頁面進(jìn)行高速緩存,并且在對文件的映射視圖進(jìn)行操作時(shí)不立即更新文件的磁盤映像。如果需要確保你的更新被寫入磁盤,可以強(qiáng)制系統(tǒng)將修改過的數(shù)據(jù)的一部分或全部重新寫入磁盤映像中,方法是調(diào)用FlushViewOfFil函數(shù):第一個(gè)參數(shù)是包含在內(nèi)存映射文件中的視圖的一個(gè)字節(jié)的地址。該函數(shù)將你在這里傳遞的地址圓整為一個(gè)頁面邊界值。第二個(gè)參數(shù)用于指明你想要刷新的字節(jié)數(shù)。系統(tǒng)將把這個(gè)數(shù)字向FlushViewOfFile函數(shù)并且不修改任何數(shù)據(jù),那么該函數(shù)只是返回,而不將任何信息寫入磁盤。,F(xiàn)lushViewOfFil能夠保證文件的數(shù)據(jù)已經(jīng)從工作站寫入器。但是FlushViewOfFile不能保證正在共享文件的服務(wù)器已經(jīng)將數(shù)據(jù)寫入磁盤,因?yàn)榉?wù)器也許對文件的數(shù)據(jù)進(jìn)行了高速緩存。若要保證服務(wù)器寫入文件的數(shù)據(jù),每當(dāng)你為文件創(chuàng)建一個(gè)文件映射對象并且映射該文件映射對象的視圖時(shí),應(yīng)該將FILE_FLAGWRITE_THROUGH標(biāo)志傳遞給CreateFile函數(shù)。如果你使用該標(biāo)志打開該文件,那么只有當(dāng)文FlushViewOfFile記住UnmapViewOfFile函數(shù)的一個(gè)特殊的特性。如果原先使用FILE_MAP_COPY標(biāo)志來映射視圖,那么你對文件的數(shù)據(jù)所作的任何修改,實(shí)際上是對存放在系統(tǒng)的頁文件中的文件數(shù)據(jù)UnmapViewOfFile函數(shù),該函數(shù)在磁盤文件上就沒有什么可以更新,而只會(huì)釋放頁文件中的頁面,從而導(dǎo)致數(shù)據(jù)丟失。如果想保留修改后的數(shù)據(jù),必須采用別的措施。例如,你可以用同一個(gè)文件創(chuàng)建另一個(gè)文件映射對象(使用AGE_READWRITE,然后使用FILE_MAP_WRITE標(biāo)志將這個(gè)新文件映射對象映射到進(jìn)程的地址空間。之后,你可以掃描第一個(gè)視圖,尋找?guī)в蠥GE_READWRITE保護(hù)屬性的頁面。每當(dāng)你找到一個(gè)帶有該屬性的頁面時(shí),可以查看它的內(nèi)容,并且確定是否將修改了的數(shù)據(jù)寫入該文件。如果不想用新數(shù)據(jù)更新該文件,那么繼續(xù)對視圖中的剩余頁面進(jìn)行掃描,直到視圖的結(jié)尾。但是,如果你確實(shí)想要保存修改了的數(shù)據(jù)頁面,那么只需要調(diào)用MoveMemory函數(shù),將數(shù)據(jù)頁面從第一個(gè)視圖拷貝到第二個(gè)視圖。由于第二個(gè)視圖是用AGE_READWRITE保護(hù)屬性映射的,因此MoveMemory函數(shù)將更新磁盤上的實(shí)際文件內(nèi)容??梢允褂眠@種方法來確定文件的變更并保存你的文件的數(shù)據(jù)。Windows98Windows98不支持copy-on-write(寫入時(shí)拷貝)保護(hù)屬性,因此,當(dāng)掃描內(nèi)存映射文件的第一個(gè)視圖時(shí),無法測試用PAGE_READWRITE標(biāo)志做上標(biāo)記的頁現(xiàn)資源泄漏的問題。當(dāng)然,當(dāng)你的進(jìn)程終止運(yùn)行時(shí),系統(tǒng)會(huì)自動(dòng)關(guān)閉你的進(jìn)程已經(jīng)打開但是忘記關(guān)閉的任何對象。但是如果你的進(jìn)程暫時(shí)沒有終止運(yùn)行,你將會(huì)積累許多資源句柄。因此你始終都應(yīng)該編寫清楚而又“正確的”代碼,以便關(guān)閉你已經(jīng)打開的任何對象。若要關(guān)閉文件映射對象和文件對象,只需要兩次調(diào)用CloseHandle函數(shù),每個(gè)句柄調(diào)用一次:上面的代碼顯示了對內(nèi)存映射文件進(jìn)行操作所用的“預(yù)期”方法。但是,它沒有顯示,當(dāng)你調(diào)用MapViewOfFile時(shí)系統(tǒng)對文件對象和文件映射對象的使用計(jì)數(shù)的遞增情況。這個(gè)副作用是很大的,因?yàn)樗馕吨覀兛梢詫⑸厦娴拇a段重新編寫成下面的樣子:當(dāng)對內(nèi)存映射文件進(jìn)行操作時(shí),通常要打開文件,創(chuàng)建文件映射對象,然后使用文件映射對象將文件的數(shù)據(jù)視圖映射到進(jìn)程的地址空間。由于系統(tǒng)遞增了文件對象和文件映射對象的內(nèi)部使用計(jì)數(shù),因此可以在你的代碼開始運(yùn)行時(shí)關(guān)閉這些對象,以消除資源泄漏的可能性。172列出的FileRev應(yīng)用程序(“17對ANSI或Unicode文本文件的內(nèi)容進(jìn)行倒序。光盤上的17- 下。當(dāng)啟動(dòng)該程序時(shí) 圖17-7運(yùn)行FileRev時(shí)出現(xiàn)的窗FileRev應(yīng)用程序首先允許選定一個(gè)文件,然后,當(dāng)單擊ReverseFileContents(對文件內(nèi)容進(jìn)行倒序)正確的倒序,對二進(jìn)制文件不能正確地進(jìn)行倒序操作。FileRev能夠確定文本文件是ANSI文件這意味著FileRev示例應(yīng)用程序總是認(rèn)為,當(dāng)它在Windows98下運(yùn)行時(shí),它操作的是當(dāng)單擊ReverseFileContents按鈕時(shí),F(xiàn)ileRev便制作指定文件的一個(gè)拷貝,稱為FileRev.dat。它制作該拷貝的目的是,原始文件不會(huì)因?yàn)閮?nèi)容被倒序而變得無法使用。接著,F(xiàn)ileRev調(diào)用FileReverse函數(shù),該函數(shù)負(fù)責(zé)對文件進(jìn)行倒序操作。FileReverse則調(diào)用CreateFile函數(shù),打開前面,對文件內(nèi)容進(jìn)行倒序的最容易的方法是調(diào)用C運(yùn)行期函數(shù)_strrev。與所有C字符串一樣,字符串的最后一個(gè)字符必須是個(gè)0結(jié)束符。由于文本文件不以0為結(jié)束符,因此現(xiàn)在已得了文件長,你能通調(diào)用CreateFileMap函數(shù)創(chuàng)建文件映射對象。創(chuàng)建的文件映射對象的長度是dwFileSize加一個(gè)寬字符的大小(0字符來說。當(dāng)文件映射FileRev的地址空間。變量pvFile包含了MapViewOfFile函數(shù)的返回值,并指向文本文件的第一個(gè)字節(jié)。在文本文件中,每一行的結(jié)尾都是一個(gè)回車符‘\r)后隨一個(gè)換行符‘\。但是,當(dāng)調(diào)用_strrev對文件進(jìn)行倒序時(shí),這些字符也會(huì)被倒序。如果將已經(jīng)倒序的文本文件加載到文本\n\r”字符都必須重新改為它的原始順序。這個(gè)倒序操作是由下面的循環(huán)代碼進(jìn)行的:在文件被倒序后,F(xiàn)ileRev便進(jìn)行清除操作,撤消文件映射對象的視圖映象,關(guān)閉所有的FileRev必須刪除附加給文件結(jié)尾處的0字符(記住_strrev并不對結(jié)尾的0字符進(jìn)行倒序。如果沒有刪除0字符,那么倒序的文件將會(huì)多出一個(gè)字符,如果再次調(diào)用FileRev函數(shù),將無法使文件還原成它的原始樣子。若要?jiǎng)h除文件結(jié)尾處的0字符,必須后退一注意SetEndOfFile函數(shù)必須在撤消視圖的映象并且關(guān)閉文件映射對象之后調(diào)用,否則,該函數(shù)將返回FALSE,GetLastError則返回ERROR_USER_MAPPED_FILE。這個(gè)FileRev函數(shù)做的最后一件事情是產(chǎn)生一個(gè)Notepad實(shí)例,這樣,就可以查看已經(jīng)倒序的文件。圖17-8顯示了在FileRev.cpp文件上運(yùn)行FileRev圖17-8FileRev17-2FileRev上一節(jié)講過我要告訴你如何將一個(gè)16EB的文件映射到一個(gè)較小的地址空間中。當(dāng)然,你是無法做到這一點(diǎn)的。你必須映射一個(gè)只包含一小部分文件數(shù)據(jù)的文件視圖。首先映射一個(gè)文件的開頭的視圖。當(dāng)完成對文件的第一個(gè)視圖的時(shí),可以取消它的映像,然后映射一個(gè)從文件中的一個(gè)更深的位移開始的新視圖。必須重復(fù)這一操作,直到了整個(gè)文件。這使得大型內(nèi)存映射文件的處理不太方便,但是,幸好大多數(shù)文件都比較小,因此不會(huì)出現(xiàn)這個(gè)問題。讓我們看一個(gè)例子,它使用一個(gè)8GB的文件和一個(gè)32位的地址空間。下面是一個(gè)例程,它這個(gè)算法用于映射64KB(分配粒度的大小)或更小的視圖。另外,要記住,MapViewOfFile函數(shù)要求文件的位移是分配粒度大小的倍數(shù)。當(dāng)每個(gè)視圖被映射到地址空間時(shí),對0的掃描不斷進(jìn)行。當(dāng)每個(gè)64KB的文件塊已經(jīng)映射和掃描完畢時(shí),就要通過關(guān)閉文件映射對象來10KB4KB映射到另一個(gè)視圖。只要你是映射相同的文件映射對象,系統(tǒng)就會(huì)確保映射的視圖數(shù)據(jù)的相關(guān)性。例如,如果你的應(yīng)用程序改變了一個(gè)視圖中的文件內(nèi)容,那么所有其他視圖均被更新以反映這個(gè)變化。這是因?yàn)楸M管頁面多次被映射到進(jìn)程的虛擬地址空間,但是系統(tǒng)只將數(shù)據(jù)放在單個(gè)RAM頁面上。如果多個(gè)進(jìn)程映射單個(gè)數(shù)據(jù)文件的視圖,那么數(shù)據(jù)仍然是相關(guān)的,因?yàn)樵跀?shù)據(jù)文件中,每個(gè)RAM頁面只有一個(gè)實(shí)例——正是indows允許創(chuàng)建若干個(gè)由單個(gè)數(shù)據(jù)文件支持的文件映射對象。indows保證這些不同的文件映射對象的視圖具有相關(guān)性。它只能保證單個(gè)文件映射對象的多然而,當(dāng)對文件進(jìn)行操作時(shí),沒有理由使另一個(gè)應(yīng)用程序無法調(diào)用CreateFile函數(shù)以打開用ReadFile和riteFile函數(shù)來該文件的數(shù)據(jù)和將數(shù)據(jù)寫入該文件。當(dāng)然,每當(dāng)一個(gè)進(jìn)程調(diào)用這些函數(shù)時(shí),它必須從內(nèi)存緩沖區(qū)文件數(shù)據(jù)或者將文件數(shù)據(jù)寫入內(nèi)存緩沖區(qū)。該內(nèi)存緩沖區(qū)必須是進(jìn)程自己創(chuàng)建的一個(gè)緩沖區(qū),而不是映射文件使用的內(nèi)存緩沖區(qū)。當(dāng)兩個(gè)應(yīng)用程序打開同一個(gè)文件時(shí),問題就可能產(chǎn)生:一個(gè)進(jìn)程可以調(diào)用ReadFile函數(shù)來件的一部,并修它數(shù)據(jù),后用riteFile函數(shù)將數(shù)據(jù)重新寫入文件,而第二個(gè)進(jìn)程的文件映射對象卻不知道第一個(gè)進(jìn)程執(zhí)行的這些操作。由于這個(gè)原因,當(dāng)你為將被內(nèi)存映射的文件調(diào)用CreateFile函數(shù)時(shí),最好將dwShareMode的值設(shè)置為0。這樣就可以告訴系統(tǒng),你想要單獨(dú)這個(gè)文件,而其他進(jìn)程都不能打開它。只讀文件不存在相關(guān)性問題,因此它們可以作為很好的內(nèi)存映射文件。內(nèi)存映射文件決不應(yīng)該用于共享網(wǎng)絡(luò)上的可寫入文件,因?yàn)橄到y(tǒng)無法保證數(shù)據(jù)視圖的相關(guān)性。如果某個(gè)人的計(jì)算機(jī)更新了文件的內(nèi)容,其他內(nèi)存中含有原始數(shù)據(jù)的計(jì)算機(jī)將不知道它的信息已經(jīng)被修改。正如你可以使用irtualAlloc函數(shù)來確定對地址空間進(jìn)行倒序所用的初始地址一樣,你也可以使用MapViewOfFileEx函數(shù)而不是使用MapViewOfFile函數(shù)來確定一個(gè)文件被映射到某個(gè)特定的地址。請看下面的代碼:該函數(shù)的所有參數(shù)和返回值均與MapViewOfFile函數(shù)相同,唯一的差別是最后一個(gè)參數(shù)一樣,你設(shè)定的目標(biāo)地址應(yīng)該是分配粒度邊界(64KB)的倍數(shù),否則MapViewOfFileEx將返在Windows2000下,如果設(shè)定的地址不是分配粒度的倍數(shù),就會(huì)導(dǎo)致函數(shù)運(yùn)行失敗,同時(shí)GetLastError將返回132(ERROR_MAPPED_ALIGNMEN。在Windows98中,該地址將圓)NULL。MapViewOfFileEx并不設(shè)法尋找另一個(gè)地址空間來放置該文件。當(dāng)然,你可以設(shè)定NULL作為pvBaseAddress參數(shù)的值,這時(shí),當(dāng)你使用內(nèi)存映射文件與其他進(jìn)程共享數(shù)據(jù)時(shí),你可以使用MapViewOfFileEx定地址上的內(nèi)存映射文件。表是個(gè)極好的例子。在表中,每個(gè)節(jié)點(diǎn)或元素均包含列表中的另一個(gè)元素的內(nèi)存地址。若要遍歷該列表,必須知道第一個(gè)元素的地址,然后參考包含下一個(gè)元素地址的元素成員。當(dāng)使用內(nèi)存映射文件時(shí),這可能成為一個(gè)問題。如果一個(gè)進(jìn)程建立了內(nèi)存映射文件中的表,然后與另一個(gè)進(jìn)程共享該文件,那么另一個(gè)進(jìn)程就可能將文件映射到它的地址空間中的一個(gè)完全不同的位置上。當(dāng)?shù)诙€(gè)進(jìn)程視圖遍歷該表時(shí),它查看表的第一個(gè)元素,檢索下一個(gè)元素的內(nèi)存地址,然后設(shè)法下一個(gè)元素。然而,第一個(gè)節(jié)點(diǎn)中的下一個(gè)元素的地址并不是第二個(gè)進(jìn)程需要查找的地址??梢杂脙煞N辦法來解決這個(gè)問題。首先,當(dāng)?shù)诙€(gè)進(jìn)程將包含表的內(nèi)存映射文件映射MapViewOfFileEx函數(shù)而不是調(diào)用MapViewOfFile。當(dāng)然,這種方法要求第二個(gè)進(jìn)程必須知道第一個(gè)進(jìn)程原先在建立表時(shí)將文件映射到了什么地方。當(dāng)兩個(gè)應(yīng)用程序打算互相進(jìn)行交互操作時(shí)(這是非??赡艿模@就不會(huì)出現(xiàn)任何問題,因?yàn)榈刂房梢酝ㄟ^硬編碼放入兩個(gè)應(yīng)用程序,或者一個(gè)進(jìn)程可以通知另一個(gè)進(jìn)程使用另一種進(jìn)程間通信的方式,比如將消息發(fā)送到窗口。第二個(gè)方法是創(chuàng)建表的進(jìn)程將下一個(gè)節(jié)點(diǎn)所在的地址中的位移存放在每個(gè)節(jié)點(diǎn)中。這要求應(yīng)用程序?qū)⒃撐灰铺砑咏o內(nèi)存映射文件的址,以便每個(gè)節(jié)點(diǎn)。這種方法并不高明,因?yàn)樗倪\(yùn)行速度可能比較慢,它會(huì)使程序變得更大(因?yàn)榫幾g器要生成附加代碼來執(zhí)行所有的計(jì)算操作,的編譯器為使用Windows98 與xBFFFFFFF之間Windows20000 Windows98和Windows2000實(shí)現(xiàn)內(nèi)存映射文件的方法是不同的。必須知道這些差別,因在Windows98下,視圖總是映射到0x 在Windows98中,一個(gè)進(jìn)程可以調(diào)用MapViewOfFile信方式將返回的內(nèi)存地址傳遞給另一個(gè)進(jìn)程的線程。一旦該線程收到這個(gè)內(nèi)存地址,該線程就可以成功地文件映射對象的同一個(gè)視圖。但是,不應(yīng)該這樣做,原因有二。如果第一個(gè)進(jìn)程調(diào)用UnmapViewOfFile函數(shù),地址空間區(qū)域?qū)⒒謴?fù)為空閑狀態(tài),這意味著第二個(gè)進(jìn)程的線程如果嘗試視圖曾經(jīng)位于其中的內(nèi)存,會(huì)一次。如果第二個(gè)進(jìn)程內(nèi)存映射對象的視圖,那么第二個(gè)進(jìn)程中的線程應(yīng)該調(diào)用如果第一個(gè)進(jìn)程調(diào)用UnmapViewOfFile函數(shù),那么在第二個(gè)進(jìn)程也調(diào)用UnmapViewOfFile當(dāng)?shù)诙€(gè)進(jìn)程調(diào)用MapViewOfFile函數(shù)時(shí),返回的地址將與第一個(gè)進(jìn)程返回的地址相同。這樣,第一個(gè)進(jìn)程就沒有必要使用進(jìn)程間的通信方式將內(nèi)存地址傳送給第二個(gè)進(jìn)程。Windows2000實(shí)現(xiàn)內(nèi)存映射文件的方法要比Windows98好,因?yàn)閃indows2000程的地址空間中的文件數(shù)據(jù)可供之前,該進(jìn)程必須調(diào)用MapViewOfFile函數(shù)。如果一個(gè)進(jìn)程調(diào)用MapViewOfFile函數(shù),系統(tǒng)將為調(diào)用進(jìn)程的地址空間中的視圖進(jìn)行地址空間區(qū)域的倒序操作,這樣,其他進(jìn)程都將無法看到該視圖。如果另一個(gè)進(jìn)程想要同一個(gè)文件映射對象中MapViewOfFile,同時(shí),系統(tǒng)將為第二個(gè)進(jìn)程的地址空間中的視圖進(jìn)行地址空間區(qū)域的倒序操作。在Windows98中,當(dāng)文件映射對象的視圖被映射時(shí),系統(tǒng)將為整個(gè)文件映射對象保留足夠的地址空間。即使調(diào)用MapViewOfFile函數(shù)時(shí)它的參數(shù)指明你想要系統(tǒng)只映射文件映射對象的一小部分,系統(tǒng)也會(huì)為它保留足夠的地址空間。這意味著即使你規(guī)定只映射文件映射對象的一個(gè)64KB的部分,也不能將一個(gè)1GB的文件映射對象映射到一個(gè)視圖中。Windows2000的實(shí)現(xiàn)代碼在這里同樣存在很大的差別。在上面的代碼段中,兩次調(diào)用MapViewOfFile函數(shù)將導(dǎo)致Windows2000保留兩個(gè)不同的地址空間區(qū)域。第一個(gè)區(qū)域的大小是文件映射對象的大小,第二個(gè)區(qū)域的大小是文件映射對象的大小減去64KB。盡管存在兩個(gè)不而來的。在Windows98下,各個(gè)視圖具有相關(guān)性,因?yàn)樗鼈兾挥谕粋€(gè)內(nèi)存中。indows總是出色地提供各種機(jī)制,使應(yīng)用程序能夠迅速而方便地共享數(shù)據(jù)和信息。這些機(jī)制包括 、 、OLE、DDE、窗口消息(尤其是WM_COPYDA、剪貼板、郵箱、管道和套接字等。在indows中,在單個(gè)計(jì)算機(jī)上共享數(shù)據(jù)的最低層機(jī)制是內(nèi)存映射文件。不錯(cuò),如果互相進(jìn)行通信的所有進(jìn)程都在同一臺(tái)計(jì)算機(jī)上的話,上面提到的所有機(jī)制均使用內(nèi)存映射文件從事它們的煩瑣工作。如果要求達(dá)到較高的性能和較小的開銷,內(nèi)存映射文件是舉手可得的佳制。數(shù)據(jù)共享方法是通過讓兩個(gè)或多個(gè)進(jìn)程映射同一個(gè)文件映射對象的視圖來實(shí)現(xiàn)的,這意味著它們將共享物理器的同一個(gè)頁面。因此,當(dāng)一個(gè)進(jìn)程將數(shù)據(jù)寫入一個(gè)共享文件映射對象的視圖時(shí),其他進(jìn)程可以立即看到它們視圖中的數(shù)據(jù)變更情況。注意,如果多個(gè)進(jìn)程共享單個(gè)文件映射對象,那么所有進(jìn)程必須使用相同的名字來表示該文件映射對象。讓我們觀察一個(gè)例子,啟動(dòng)一個(gè)應(yīng)用程序。當(dāng)一個(gè)應(yīng)用程序啟動(dòng)時(shí),系統(tǒng)調(diào)用CreateFile函數(shù),打開磁盤上的.exe文件。然后系統(tǒng)調(diào)用CreateFileMap函數(shù),創(chuàng)建一個(gè)文件映射對象。最后,系統(tǒng)代表新創(chuàng)建的進(jìn)程調(diào)用MapViewOfFileEx函數(shù)(它帶有SEC_IMAGE標(biāo)志,這.exe文件就可以映射到進(jìn)程的地址空間。這里調(diào)用的是MapViewOfFileEx,而不是MapViewOfFile在.exe文件映像中的址中。系統(tǒng)創(chuàng)建該進(jìn)程的主線程,將該映射視圖的可執(zhí)行代碼的第一個(gè)字節(jié)的地址放入線程的指令指針,然后CPU啟動(dòng)該代碼的運(yùn)行。如果用戶運(yùn)行同一個(gè)應(yīng)用程序的第二個(gè)實(shí)例,系統(tǒng)就認(rèn)為規(guī)定的.exe文件已經(jīng)存在一個(gè)文件映射對象,因此不會(huì)創(chuàng)建新的文件對象或者文件映射對象。相反,系統(tǒng)將第二次映射該文件的一個(gè)視圖,這次是在新創(chuàng)建的進(jìn)程的地址空間環(huán)境中映射的。系統(tǒng)所做的工作是將相同的文件同時(shí)映射到兩個(gè)地址空間。顯然,這是對內(nèi)存的更有效的使用,因?yàn)閮蓚€(gè)進(jìn)程將共享包含正在執(zhí)行的這部分代碼的物理器的同一個(gè)頁面。與所有內(nèi)核對象一樣,可以使用3種方法與多個(gè)進(jìn)程共享對象,這3種方法是句柄繼承性、句柄命名和句柄。關(guān)于這3種方法的詳細(xì)說明,參見第3章的內(nèi)容。行時(shí)都要?jiǎng)?chuàng)建一些數(shù)據(jù),并且需要將數(shù)據(jù)傳送給其他進(jìn)程,或者與其他進(jìn)程共享。如果應(yīng)用程序必須在磁盤驅(qū)動(dòng)器上創(chuàng)建數(shù)據(jù)文件,并且將數(shù)據(jù)在磁盤上以便對它進(jìn)行共享,那么這將是非常不方便的。公司認(rèn)識(shí)到了這一點(diǎn),并且增加了一些功能,以便創(chuàng)建由系統(tǒng)的頁文件支持的內(nèi)存映射文件,而不是由硬盤文件支持的內(nèi)存映射文件。這個(gè)方法與創(chuàng)建內(nèi)存映射磁盤文件所用的方法幾乎相同,不同之處是它更加方便。一方面,它不必調(diào)用CreateFile函數(shù),因?yàn)槟悴皇且獎(jiǎng)?chuàng)建或打開一個(gè)指定的文件,你只需要像通常那樣調(diào)用CreateFileMap函數(shù),并且傳遞INVALID_HANDLE_VALUE作為hFile參數(shù)。這將告訴系統(tǒng),你不是創(chuàng)建其物理器駐留在磁盤上的文件中的文件映射對象,相反,你想讓系統(tǒng)從它的頁文件中提交物理器。分配的器的數(shù)量由CreateFileMap函數(shù)的dwumSizeHigh和dwumSizeLow兩個(gè)當(dāng)創(chuàng)建了文件映射對象并且將它的一個(gè)視圖映射到進(jìn)程的地址空間之后,就可以像使用任何內(nèi)存域樣使用。果你想與他進(jìn)程享數(shù)據(jù),調(diào)用CreateFileMap函數(shù),并傳遞一個(gè)以0結(jié)尾的字符串作為pszName參數(shù)。然后,想要該器的其他進(jìn)程就可以調(diào)用CreateFileMap或OpenFileMap函數(shù),并傳遞相同的名字。當(dāng)進(jìn)程不再想要文件映射對象時(shí),該進(jìn)程應(yīng)該調(diào)用CloseHandle函數(shù)。當(dāng)所有句柄均 如果上面這個(gè)對CreateFile函數(shù)的調(diào)用失敗,它將返回INALID_HANDLE_ALUE。但是,編寫這個(gè)代碼的程序員沒有測試一下,看文件是否已經(jīng)創(chuàng)建成功。當(dāng)CreateFileMap函數(shù)被調(diào)用時(shí),在hFile參數(shù)中傳遞了INVALID_HANDLE_ALUE,這使得系統(tǒng)創(chuàng)建一個(gè)使用來自頁文件而不是來自指定的磁盤文件器的文件映像。使用內(nèi)存映射文件的任何輔助代碼都能夠正確地運(yùn)行。但是,當(dāng)文件映射對象被撤消時(shí),寫入文件映射器(頁文件)的全部數(shù)據(jù)將被系統(tǒng)撤消。這時(shí),程序員就坐在那里絞盡腦汁,不知道問題究竟出在哪里。因此,必須始終檢查CreateFile函數(shù)的返回值,以確定是否出現(xiàn)了錯(cuò)誤,因?yàn)镃reateFile運(yùn)行失敗的原因太多了。 然后單擊CreateMapOfData(創(chuàng)建數(shù)據(jù)映像)按鈕。當(dāng)進(jìn)行這項(xiàng)操作時(shí),MMFShare調(diào)用CreateFileMap函數(shù),創(chuàng)建一個(gè)由系統(tǒng)的頁文件支持的4KB內(nèi)存映射文件對象,并將該對命名為MMFSharedData。如果MMFShare發(fā)現(xiàn)已經(jīng)存在一個(gè)帶有這個(gè)名字的文件映射對象,它就顯示一個(gè)消息框,告訴你它不能創(chuàng)建該對象。如果MMFShare地創(chuàng)建了該對象,那么它將進(jìn)一步將文件的視圖映射到進(jìn)程的地址空間,并將數(shù)據(jù)從編輯控件拷貝到內(nèi)存 圖17-9運(yùn)行MMFShare時(shí)出現(xiàn)的當(dāng)數(shù)據(jù)被拷貝后,MMFShare就撤消文件的視圖,使CreateMapOfData按鈕不起作用,并激活CloseMapOfData(關(guān)閉數(shù)據(jù)映像)按鈕。這時(shí),命名為MMFSharedData的內(nèi)存映如果這時(shí)轉(zhuǎn)入MMFShare的另一個(gè)實(shí)例,并且單擊該實(shí)例的OpenMapAndGet(打開映像并獲取數(shù)據(jù))按鈕,那么MMFShare將設(shè)法通過調(diào)用OpenFileMap函數(shù),尋找一個(gè)稱為MMFSharedData的文件映射對象。如果無法找到帶有該名字的對象,MMFShare就會(huì)顯MMFShare找到了這個(gè)對象,它將把該對象的視圖映射到它的進(jìn)程的地址空間,將數(shù)據(jù)從內(nèi)存映射文件拷貝到框的編輯控件中,然后撤消它的映像,關(guān)閉文件映射對象。好極了,你已經(jīng)成功地將數(shù)據(jù)從一個(gè)進(jìn)程傳送到另一個(gè)進(jìn)程??蛑械腃loseMapOfData(關(guān)閉數(shù)據(jù)映像)按鈕用于關(guān)閉文件映射對象,它能夠釋放頁文件中的器。如果不存在任何文件映射對象,那么MMFShare的其他實(shí)例將無法打開文件映像并從中取出數(shù)據(jù)。另外,如果一個(gè)實(shí)例已經(jīng)創(chuàng)建了一個(gè)內(nèi)存映射文件,那么其他實(shí)例均不得創(chuàng)建內(nèi)存映射文件并改寫文件中包含的數(shù)據(jù)。17-3MMFShare在迄今為止介紹的所有內(nèi)存映射文件中,我們發(fā)現(xiàn)系統(tǒng)要求為內(nèi)存映射文件提交的所有存儲(chǔ)器必須是在磁盤上的數(shù)據(jù)文件中或者是在頁文件中。這意味著我們不能根據(jù)我們的喜好來有效地使用器。讓我們回到第15章中介紹電子表格的內(nèi)容上來,比如說,你想要與另一個(gè)進(jìn)程共享整個(gè)電子表格。如果我們使用內(nèi)存映射文件,那么必須為整個(gè)電子表格提交物理器:如果CELLDATA結(jié)構(gòu)的大小是128字節(jié),那么這個(gè)數(shù)組需要6553600(200x256x128)顯然,我們寧愿將電子表格作為一個(gè)文件映射對象來共享,而不必預(yù)先提交所有的物理存儲(chǔ)器。CreateFileMap函數(shù)為這種操作提供了法,即可以在fdwProtect參數(shù)中設(shè)定 只有當(dāng)創(chuàng)建由系統(tǒng)的頁文件支持的文件映射對象時(shí),這些標(biāo)志才有意義。MIT標(biāo)志能使retFieMap從系統(tǒng)的頁文件中提交器。如果兩個(gè)標(biāo)志都不設(shè)定,其結(jié)果也當(dāng)調(diào)用CreateFileMap函數(shù)并傳遞SEC_RESEVE標(biāo)志時(shí),系統(tǒng)并不從它的頁文件中提交物理器,它只是返回文件映射對象的一個(gè)句柄。這時(shí)可以調(diào)用MapViewOfFile或MapViewOfFileEx函數(shù),創(chuàng)建該文件映射對象的視圖。MapViewOfFile和MapViewOfFileEx將保留一個(gè)地址空間區(qū)域,并且不提交支持該區(qū)域的任何物理器。對保留區(qū)域中的內(nèi)存地址進(jìn)行的任何嘗試均將導(dǎo)致線程?,F(xiàn)在我們得到的是一個(gè)保留的地址空間區(qū)域和用于標(biāo)識(shí)該區(qū)域的文件映射對象的句柄。其他進(jìn)程可以使用相同的文件映射對象來映射同一個(gè)地址空間區(qū)域的視圖。物理器仍然沒有被提交給該區(qū)域。如果其他進(jìn)程中的線程試圖它們區(qū)域中的視圖的內(nèi)存地址,這些線程將會(huì)。下面是令人感的一些事情。若要將物理器提交給共享區(qū)域,線程需要做的操作只15irtualAlloc函數(shù)將物理器提交給內(nèi)存映射視圖區(qū)域,用irtualAlloc函數(shù)將器提交給開始時(shí)通過調(diào)用帶有MEM_RESEVE標(biāo)志的irtualAlloc函數(shù)而保留的區(qū)域一樣。而且,就像你可以提交稀疏地存在irtualAlloc保留的在MapViewOfFile或MapViewOfFileEx保的將用MapViewOfFil或MapViewOfFileEx保留的區(qū)域時(shí),已經(jīng)映射了相同文件映射對象視圖的所有進(jìn)程這時(shí)就能夠成功地 已經(jīng)提交的頁Windows98通常情況下,當(dāng)給irtualAlloc函數(shù)傳遞的內(nèi)存地址位于0x 0x7FFFFFF,irtualAlloc的運(yùn)行就會(huì)失敗。但是,當(dāng)將物理器提交給使用SEC_RESEVE標(biāo)志創(chuàng)建的內(nèi)存映射文件時(shí),必須調(diào)用irtualAlloc函數(shù),傳遞一個(gè)位于0x 至xBFFFFFFF之間的內(nèi)存地址。Windows98知道你正在把器提交給一個(gè)保留的內(nèi)存映射文件,并且讓這個(gè)函數(shù)調(diào)用取得成功。注意在Windows2000下,無法使用Virtual函數(shù)從使用SEC_RESERVE標(biāo)志保留的內(nèi)存映射文件中釋放器。但是,Windows98允許在這種情況下調(diào)用VirtualN文件系統(tǒng)(NTFS5)提供了對稀疏文件的支持。這是個(gè)非常出色的新特性。使用這個(gè)器包含在通常的磁盤文件中,而不是在系統(tǒng)的頁文件中。下面是如何使用稀疏文件特性的一個(gè)例子。比如,你想要?jiǎng)?chuàng)建一個(gè)MMF文件,以便存放記錄的音頻數(shù)據(jù)。當(dāng)用戶說話時(shí),你想要將數(shù)字音頻數(shù)據(jù)寫入內(nèi)存緩沖區(qū),并且讓該緩沖區(qū)得MMF當(dāng)然是在你的代碼中實(shí)現(xiàn)這個(gè)要求的最容易和最有效的方法。問題是你不知道用戶在單擊Stop(停止)按鈕之前講了多長時(shí)間。你可能需要一個(gè)足5分鐘或5小時(shí)的數(shù)據(jù),這兩個(gè)時(shí)間長度的差別太大了。但是,當(dāng)使用稀疏MM時(shí),數(shù)據(jù)文件的大小確實(shí)無關(guān)緊要。174列出的MMFSparse應(yīng)用程序(“17MMFSparse.exe)顯示了如何創(chuàng)建一個(gè)由NTFS5支持的內(nèi)存映射文件。該應(yīng)用程序的源代碼和資源文件位于本書所附光盤上的17-MMFSparse下。當(dāng)啟動(dòng)該程序時(shí),便會(huì)出現(xiàn)圖當(dāng)單擊Createa1MB(1024K
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年四川貨運(yùn)從業(yè)考試試題及答案詳解
- 2024年度建筑工程碎石材料采購合同模板2篇
- 2024年建筑排水工程分包標(biāo)準(zhǔn)協(xié)議模板版B版
- 2024年度高科技產(chǎn)業(yè)園區(qū)土地使用權(quán)永久出讓及稅收優(yōu)惠協(xié)議3篇
- 2024年物資運(yùn)送聯(lián)盟協(xié)議
- 2025彎腳質(zhì)檢科長業(yè)績合同書
- 2024年城市綠化帶施工安裝及養(yǎng)護(hù)管理合同2篇
- 2025電力施工合同
- 《連云港特色美食》課件
- 咖啡連鎖合同管理細(xì)則
- 2023-2024學(xué)年山東省膠州市初中語文九年級上冊期末自測測試題
- 人力資源專員招聘筆試題
- LY/T 1646-2005森林采伐作業(yè)規(guī)程
- GB/T 7714-2015信息與文獻(xiàn)參考文獻(xiàn)著錄規(guī)則
- GB/T 7531-2008有機(jī)化工產(chǎn)品灼燒殘?jiān)臏y定
- GB/T 19963.1-2021風(fēng)電場接入電力系統(tǒng)技術(shù)規(guī)定第1部分:陸上風(fēng)電
- GB/T 13586-2006鋁及鋁合金廢料
- 二年級上冊數(shù)學(xué)試題-應(yīng)用題復(fù)習(xí)6-人教新課標(biāo)(2014秋)(無答案)
- 麗聲北極星分級繪本第一級上Tiger-Is-Coming課件
- 2023年哈工大模電大作業(yè)
- 高考作文 論證方法匯總
評論
0/150
提交評論