萬網(wǎng)域名申請網(wǎng)站全自動推廣引流軟件
文件的輸入輸出(I/O)服務(wù)是操作系統(tǒng)的重要部分。Windows提供了一類API函數(shù)來讀、寫和管理磁盤文件。MFC將這些函數(shù)轉(zhuǎn)化為一個面向?qū)ο蟮念悺狢File,它允許將文件視為可以由CFile成員函數(shù)操作的對象,如Read和Write等。CFile類實(shí)現(xiàn)了程序開發(fā)者執(zhí)行底層文件I/O需要的大部分功能。
并不是在任何時(shí)候使用CFile類都是方便的,特別是要與底層設(shè)備(如COM口、設(shè)備驅(qū)動)進(jìn)行交互的時(shí)候,所以本節(jié)主要討論管理文件的API函數(shù)。事實(shí)上,了解這些函數(shù)之后,自然就會使用CFile類了。
8.1.1 創(chuàng)建和讀寫文件
使用API函數(shù)讀寫文件時(shí),首先要使用CreateFile函數(shù)創(chuàng)建文件對象(即打開文件),調(diào)用成功會返回文件句柄;然后以此句柄為參數(shù)調(diào)用ReadFile和WriteFile函數(shù),進(jìn)行實(shí)際的讀寫操作;最后調(diào)用CloseHandle函數(shù)關(guān)閉不再使用的文件對象句柄。
1.打開和關(guān)閉文件
CreateFile是一個功能相當(dāng)強(qiáng)大的函數(shù),Windows下的底層設(shè)備差不多都是由它打開的。它可以創(chuàng)建或打開文件、目錄、物理磁盤、控制臺緩沖區(qū)、郵槽和管道等。調(diào)用成功后,函數(shù)返回能夠用來訪問此對象的句柄,其原型如下。
HANDLE CreateFile ( LPCTSTR lpFileName, // 要創(chuàng)建或打開的對象的名稱 DWORD dwDesiredAccess, // 文件的存取方式 DWORD dwShareMode, // 共享屬性 LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全屬性 DWORD dwCreationDisposition, // 文件存在或不存在時(shí)系統(tǒng)采取的行動 DWORD dwFlagsAndAttributes, // 新文件的屬性 HANDLE hTemplateFile // 一個文件模板的句柄
);
各參數(shù)含義如下。
(1)lpFileName參數(shù)是要創(chuàng)建或打開的對象的名稱。如果打開文件,直接在這里指定文件名稱即可;如果操作對象是第一個串口,則要指定“COM1”為文件名,然后就可以像操作文件一樣操作串口了;如果要打開本地電腦上的一個服務(wù),要以“"\\.\服務(wù)名稱"
”為文件名,其中的“.”代表本地機(jī)器;也可以使用CreateFile打開網(wǎng)絡(luò)中其他主機(jī)上的文件,此時(shí)的文件名應(yīng)該是“\\主機(jī)名\共享目錄名\文件名
”。
(2)dwDesiredAcces參數(shù)是訪問方式,它指定了要對打開的對象進(jìn)行何種操作。指定GENERIC_READ標(biāo)志表示以只讀方式打開;指定GENERIC_WRITE標(biāo)志表示以只寫方式打開;指定這兩個值的組合,表示要同時(shí)對打開的對象進(jìn)行讀寫操作。
(3)dwShareMode參數(shù)指定了文件對象的共享模式,表示文件打開后是否允許其他代碼以某種方式再次打開這個文件,它可以是下列值的一個組合:
0 不允許文件再被打開 C語言中的fopen函數(shù)就是這樣打開文件的
FILE_SHARE_DELETE 允許以后的程序代碼對文件刪除文件(Win98系列的系統(tǒng)不支持這 個標(biāo)志)
FILE_SHARE_READ 允許以后的程序代碼以讀方式打開文件
FILE_SHARE_WRITE 允許以后的程序代碼以寫方式打開文件
(4)dwCreationDisposition參數(shù)指定了當(dāng)文件已存在或者不存在時(shí)系統(tǒng)采取的動作。在這里設(shè)置不同的標(biāo)志就可以決定究竟是要打開文件,還是要創(chuàng)建文件。參數(shù)的可能取值如下:
CREATE_ALWAYS 創(chuàng)建新文件。如果文件存在,函數(shù)會覆蓋這個文件,清除存在的屬性
CREATE_NEW 創(chuàng)建新文件。如果文件存在,函數(shù)執(zhí)行失敗
OPEN_ALWAYS 如果文件已經(jīng)存在,就打開它,不存在則創(chuàng)建新文件
OPEN_EXISTING 打開存在的文件。如果文件不存在,函數(shù)執(zhí)行失敗
TRUNCATE_EXISTING 打開文件并將文件截?cái)酁榱?#xff0c;當(dāng)文件不存在時(shí)函數(shù)執(zhí)行失敗
(5)dwFlagsAndAttributes參數(shù)用來指定新建文件的屬性和標(biāo)志。文件屬性可以是下面這些值的組合:
FILE_ATTRIBUTE_ARCHIVE 標(biāo)記歸檔屬性
FILE_ATTRIBUTE_HIDDEN 標(biāo)記隱藏屬性
FILE_ATTRIBUTE_READONLY 標(biāo)記只讀屬性
FILE_ATTRIBUTE_READONLY 標(biāo)記系統(tǒng)屬性
FILE_ATTRIBUTE_TEMPORARY 臨時(shí)文件。操作系統(tǒng)會盡量把所有文件的內(nèi)容保持在內(nèi) 存中以加快存取速度。使用完后要盡快將它刪除
此參數(shù)還可同時(shí)指定對文件的操作方式,下面是一些比較常用的方式:
FILE_FLAG_DELETE_ON_CLOSE 文件關(guān)閉后系統(tǒng)立即自動將它刪除
FILE_FLAG_OVERLAPPED 使用異步讀寫文件的方式
FILE_FLAG_WRITE_THROUGH 系統(tǒng)不會對文件使用緩存,文件的任何改變都會被系統(tǒng) 立即寫入硬盤
(6)hTemplateFile參數(shù)指定了一個文件模板句柄。系統(tǒng)會復(fù)制該文件模板的所有屬性到當(dāng)前創(chuàng)建的文件中。Windows 98系列的操作系統(tǒng)不支持它,必須設(shè)為NULL。
打開或創(chuàng)建文件成功時(shí),函數(shù)返回文件句柄,失敗時(shí)返回INVALID_HANDLE_VALUE(-1)。如果想再詳細(xì)了解失敗的原因,可以繼續(xù)調(diào)用GetLastError函數(shù)。
用不同的參數(shù)組合調(diào)用CreateFile函數(shù)可以完成不同的功能,例如,下面的代碼為讀取數(shù)據(jù)打開了一個存在的文件。
HANDLE hFile;
hFile = CreateFile("myfile.txt", // 要創(chuàng)建的文件 GENERIC_WRITE, // 要寫這個文件 0, // 不共享 NULL, // 默認(rèn)安全屬性 CREATE_ALWAYS, // 如果存在就覆蓋 FILE_ATTRIBUTE_NORMAL, // 普通文件 NULL); // 沒有模板 if(hFile == INVALID_HANDLE_VALUE) { ……// 不能夠打開文件 }
要關(guān)閉打開的文件,直接以CreateFile返回的文件句柄調(diào)用CloseHandle函數(shù)即可。
2.移動文件指針
系統(tǒng)為每個打開的文件維護(hù)一個文件指針,指定對文件的下一個讀寫操作從什么位置開始。隨著數(shù)據(jù)的讀出或?qū)懭?#xff0c;文件指針也隨之移動。當(dāng)文件剛被打開時(shí),文件指針處于文件的頭部。有時(shí)候需要隨機(jī)讀取文件內(nèi)容,這就需要先調(diào)整文件指針,SetFilePointer函數(shù)提供了這個功能,原型如下。
DWORD SetFilePointer ( HANDLE hFile, // 文件句柄 LONG lDistanceToMove, // 要移動的距離 PLONG lpDistanceToMoveHigh, // 移動距離的高32位,一般設(shè)置為NULL DWORD dwMoveMethod // 移動的模式
);
dwMoveMethod參數(shù)指明了從什么地方開始移動,可以是下面的一個值:
FILE_BEGIN 開始移動位置為0,即從文件頭部開始移動
FILE_CURRENT 開始移動位置是文件指針的當(dāng)前值
FILE_END 開始移動位置是文件的結(jié)尾,即從文件尾開始移動
函數(shù)執(zhí)行失敗返回-1,否則返回新的文件指針的位置。
文件指針也可以移動到所有數(shù)據(jù)的后面,比如現(xiàn)在文件的長度是100 KB,但還是可以成功的將文件指針移動到1000 KB的位置。這樣做可以達(dá)到擴(kuò)展文件長度的目的。
SetEndOfFile函數(shù)可以截?cái)嗷蛘邤U(kuò)展文件。該函數(shù)移動指定文件的結(jié)束標(biāo)志(end-of-file,EOF)到文件指針指向的位置。如果文件擴(kuò)展,舊的EOF位置和新的EOF位置間的內(nèi)容是未定義的。SetEndOfFile函數(shù)的用法如下。
BOOL SetEndOfFile(HANDLE hFile);
截?cái)嗷蛘邤U(kuò)展文件時(shí),要首先調(diào)用SetFilePointer移動文件指針,然后再調(diào)用SetFilePointer函數(shù)設(shè)置新的文件指針位置為EOF。
3.讀寫文件
讀寫文件的函數(shù)是ReadFile和WriteFile,這兩個函數(shù)既可以同步讀寫文件,又可以異步讀寫文件。而函數(shù)ReadFileEx和WriteFileEx只能異步讀寫文件。
從文件讀取數(shù)據(jù)的函數(shù)是ReadFile,向文件寫入數(shù)據(jù)的函數(shù)是WriteFile,操作的開始位置由文件指針指定。這兩個函數(shù)的原型如下。
BOOL ReadFile(
HANDLE hFile, // 文件句柄
LPVOID lpBuffer, // 指向一個緩沖區(qū),函數(shù)會將讀出的數(shù)據(jù)返回到這里
DWORD nNumberOfBytesToRead, // 要求讀入的字節(jié)數(shù)
LPDWORD lpNumberOfBytesRead, // 指向一個DWORD類型的變量, // 用于返回實(shí)際讀入的字節(jié)數(shù)
LPOVERLAPPED lpOverlapped // 以便設(shè)為NULL
);
BOOL WriteFile (hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
當(dāng)用WriteFile寫文件時(shí),寫入的數(shù)據(jù)通常被Windows暫時(shí)保存在內(nèi)部的高速緩存中,等合適的時(shí)候再一并寫入磁盤。如果一定要保證所有的數(shù)據(jù)都已經(jīng)被傳送,可以強(qiáng)制使用FlushFileBuffers函數(shù)來清空數(shù)據(jù)緩沖區(qū),函數(shù)的惟一參數(shù)是要操作的文件句柄。
BOOL FlushFileBuffers (HANDLE hFile );
4.鎖定文件
當(dāng)對文件數(shù)據(jù)的一致性要求較高時(shí),為了防止程序在寫入的過程中其他進(jìn)程剛好在讀取寫入?yún)^(qū)域的內(nèi)容,可以對已打開文件的某個部分進(jìn)行加鎖,這就可以防止其他進(jìn)程對該區(qū)域進(jìn)行讀寫。加鎖和解鎖的函數(shù)是LockFile和UnlockFile,它們的原形如下。
BOOL LockFile( HANDLE hFile, // 文件句柄 DWORD dwFileOffsetLow, // 加鎖的開始位置 DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow, // 加鎖的區(qū)域的大小 DWORD nNumberOfBytesToLockHigh
);
UnlockFile ( hFile, dwFileOffsetLow, dwFileOffsetHigh, nNumberOfBytesToUnlockLow, nNumberOfBytesToUnlockHigh);
dwFileOffsetLow和dwFileOffsetHigh參數(shù)組合起來指定了加鎖區(qū)域的開始位置,nNumberOfBytesToLockLow和nNumberOfBytesToLockHigh參數(shù)組合起來指定了加鎖區(qū)域的大小。這兩個參數(shù)都指定了一個64位的值,在Win32中,只使用32位就夠了。
如果加鎖文件的進(jìn)程終止,或者文件關(guān)閉時(shí)還未解鎖,操作系統(tǒng)會自動解除對文件的鎖定。但是,操作系統(tǒng)解鎖文件花費(fèi)的時(shí)間取決于當(dāng)前可用的系統(tǒng)資源。因此,進(jìn)程終止時(shí)最好顯式地解鎖所有已鎖定的文件,以免造成這些文件無法訪問。
8.1.2 獲取文件信息
1.獲取文件類型
Windows下的許多對象都稱之為文件,如果想知道一個文件句柄究竟對應(yīng)什么對象,可以使用GetFileType函數(shù),原型如下。
DWORD GetFileType(HANDLE hFile);
函數(shù)的返回值說明了文件類型,可以是下面的一個值:
FILE_TYPE_CHAR 指定文件是字符文件,通常是LPT設(shè)備或控制臺
FILE_TYPE_DISK 指定文件是磁盤文件
FILE_TYPE_PIPE 指定文件是套節(jié)字,一個命名的或未命名的管道
FILE_TYPE_UNKNOWN 不能識別指定文件,或者函數(shù)調(diào)用失敗
2.獲取文件大小
如果確定操作的對象是磁盤文件,還可以使用GetFileSize函數(shù)取得這個文件的長度。
DWORD GetFileSize( HANDLE hFile, // 文件句柄 LPDWORD lpFileSizeHigh // 用于返回文件長度的高字。可以指定這個參數(shù)為NULL
);
函數(shù)執(zhí)行成功將返回文件大小的低雙字,如果lpFileSizeHigh參數(shù)不是NULL,函數(shù)將文件大小的高雙字放入它指向的DWORD變量中。
如果函數(shù)執(zhí)行失敗,并且lpFileSizeHigh是NULL,返回值將是INVALID_FILE_SIZE;如果函數(shù)執(zhí)行失敗,但lpFileSizeHigh不是NULL,返回值是INVALID_FILE_SIZE,進(jìn)一步調(diào)用GetLastError會返回不為NO_ERROR的值。
如果返回值是INVALID_FILE_SIZE,應(yīng)用程序必須調(diào)用GetLastError來確定函數(shù)調(diào)用是否成功。原因是,當(dāng)lpFileSizeHigh不為NULL或者文件大小為0xffffffff時(shí),函數(shù)雖然調(diào)用成功了,但依然會返回INVALID_FILE_SIZE。這種情況下,GetLastError會返回NO_ERROR來響應(yīng)成功。
3.獲取文件屬性
如果要查看文件或者目錄的屬性,可以使用GetFileAttributes函數(shù),它會返回一系列FAT風(fēng)格的屬性信息。
DWORD GetFileAttributes(LPCTSTR lpFileName); // lpFileName指定了文件或者目錄的名稱
函數(shù)執(zhí)行成功,返回值包含了指定文件或目錄的屬性信息,可以是下列取值的組合:
FILE_ATTRIBUTE_ARCHIVE 文件包含歸檔屬性
FILE_ATTRIBUTE_COMPRESSED 文件和目錄被壓縮
FILE_ATTRIBUTE_DIRECTORY 這是一個目錄
FILE_ATTRIBUTE_HIDDEN 文件包含隱含屬性
FILE_ATTRIBUTE_NORMAL 文件沒有其他屬性
FILE_ATTRIBUTE_READONLY 文件包含只讀屬性
FILE_ATTRIBUTE_SYSTEM 文件包含系統(tǒng)屬性
FILE_ATTRIBUTE_TEMPORARYT 文件是一個臨時(shí)文件
這些屬性對目錄也同樣適用。INVALID_FILE_ATTRIBUTES(0xFFFFFFFF)是函數(shù)執(zhí)行失敗后的返回值。
下面是快速檢查某個文件或目錄是否存在的自定義函數(shù),可以將它用在自己的工程中。
BOOL FileExists(LPCTSTR lpszFileName, BOOL bIsDirCheck)
{ // 試圖取得文件的屬性 DWORD dwAttributes = GetFileAttributes(lpszFileName); if(dwAttributes == 0xFFFFFFFF) return FALSE; if ((dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { if (bIsDirCheck) return TRUE; else return FALSE; } else { if (!bIsDirCheck) return TRUE; else return FALSE; }
}
第2個參數(shù)bIsDirCheck指定要檢查的對象是目錄還是文件。
與GetFileAttributes相對應(yīng)的函數(shù)是SetFileAttributes,這個函數(shù)用來設(shè)置文件屬性。
BOOL SetFileAttributes( LPCTSTR lpFileName, // 目標(biāo)文件名稱 DWORD dwFileAttributes // 要設(shè)置的屬性值
);
8.1.3 常用文件操作
1.拷貝文件
拷貝文件的函數(shù)是CopyFile和CopyFileEx,其作用都是復(fù)制一個存在的文件到一個新文件中。CopyFile函數(shù)的用法如下。
BOOL CopyFile( LPCTSTR lpExistingFileName, // 指定已存在的文件的名稱 LPCTSTR lpNewFileName, // 指定新文件的名稱 BOOL bFailIfExists // 如果指定的新文件存在是否按出錯處理
);
CopyFileEx函數(shù)的附加功能是允許指定一個回調(diào)函數(shù),在拷貝過程中,函數(shù)每拷貝完一部分?jǐn)?shù)據(jù),就會調(diào)用回調(diào)函數(shù)。用戶在回調(diào)函數(shù)中可以指定是否停止拷貝,還可以顯示進(jìn)度條來指示拷貝的進(jìn)度。
2.刪除文件
刪除文件的函數(shù)是DeleteFile,僅有的參數(shù)是要刪除文件的名稱。
BOOL DeleteFile(LPCTSTR lpFileName);
如果應(yīng)用程序試圖刪除不存在的文件,DeleteFile將執(zhí)行失敗。如果目標(biāo)文件是只讀的,函數(shù)也會執(zhí)行失敗,出錯代碼為ERROR_ACCESS_DENIED。為了刪除只讀文件,先要去掉其只讀屬性。
DeleteFile函數(shù)可以標(biāo)識一個文件為“關(guān)閉時(shí)刪除”。因此,直到最后一個到此文件的句柄關(guān)閉之后,文件才會被刪除。
下面的自定義函數(shù)RecursiveDelete示例了如何刪除指定目錄下的所有文件和子目錄。
void RecursiveDelete(CString szPath)
{ CFileFind ff; // MFC將查找文件的API封裝到了CFileFind類 讀者可參考下面的框架使用這個類 CString strPath = szPath; // 說明要查找此目錄下的所有文件 if(strPath.Right(1) != "\\") strPath += "\\"; strPath += "*.*"; BOOL bRet; if(ff.FindFile(strPath)) { do { bRet = ff.FindNextFile(); if(ff.IsDots()) // 目錄為“.”或者“..”? continue; strPath = ff.GetFilePath(); if(!ff.IsDirectory()) { // 刪除此文件 ::SetFileAttributes(strPath, FILE_ATTRIBUTE_NORMAL); ::DeleteFile(strPath); } else { // 遞歸調(diào)用 RecursiveDelete(strPath); // 刪除此目錄(RemoveDirectory只能刪除空目錄) ::SetFileAttributes(strPath, FILE_ATTRIBUTE_NORMAL); ::RemoveDirectory(strPath); } } while(bRet); }
}
用DeleteFile函數(shù)刪除的文件不會被放到回收站,它們將永遠(yuǎn)丟失,所以請小心使用RecursiveDelete函數(shù)。
3.移動文件
移動文件的函數(shù)是MoveFile和MoveFileEx函數(shù)。它們的主要功能都是用來移動一個存在的文件或目錄。MoveFile函數(shù)用法如下。
BOOL MoveFile( LPCTSTR lpExistingFileName, // 存在的文件或目錄 LPCTSTR lpNewFileName // 新的文件或目錄
);
當(dāng)需要指定如何移動文件時(shí),請使用MoveFileEx函數(shù)。
BOOL MoveFileEx(LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, DWORD dwFlags);
dwFlags參數(shù)可以是下列值的組合:
MOVEFILE_DELAY_UNTIL_REBOOT 函數(shù)并不馬上執(zhí)行,而是在操作系統(tǒng)下一此重新啟動時(shí)才移動文件。在AUTOCHK執(zhí)行之后,系統(tǒng)立即移動文件,這是在創(chuàng)建任何分頁文件之前進(jìn)行的。因此,這個值使函數(shù)能夠刪除上一次運(yùn)行時(shí)使用的分頁文件。只有擁有管理員權(quán)限的用戶才可以使用這個值
MOVEFILE_REPLACE_EXISTING 如果目標(biāo)文件已存在的話,就將它替換掉
MOVEFILE_WRITE_THROUGH 直到文件實(shí)際從磁盤移除之后函數(shù)才返回
如果指定了MOVEFILE_DELAY_UNTIL_REBOOT標(biāo)記,lpNewFileName參數(shù)可以指定為NULL,這種情況下,當(dāng)系統(tǒng)下一次啟動時(shí),操作系統(tǒng)會刪除lpExistingFileName參數(shù)指定的文件。
8.1.4 檢查PE文件有效性的例子
PE文件格式是任何可執(zhí)行模塊或者DLL的文件格式,PE文件以64字節(jié)的DOS文件頭(IMAGE_DOS_HEADER結(jié)構(gòu))開始,之后是一小段DOS程序,然后是248字節(jié)的NT文件頭(IMAGE_NT_HEADERS結(jié)構(gòu))。NT文件頭的偏移地址由IMAGE_DOS_HEADER結(jié)構(gòu)的e_lfanew成員給出。
檢查文件是不是有效PE文件的一個方法是檢查IMAGE_DOS_HEADER和IMAGE_NT_HEADERS結(jié)構(gòu)是否有效。IMAGE_DOS_HEADER結(jié)構(gòu)定義如下。
typedef struct _IMAGE_DOS_HEADER
{ WORD e_magic; // DOS可執(zhí)行文件標(biāo)記,為“MZ”。依此識別DOS頭是否有效 ... // 其他成員,沒什么用途 LONG e_lfanew; // IMAGE_NT_HEADERS結(jié)構(gòu)的地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
IMAGE_NT_HEADERS結(jié)構(gòu)定義如下。
typedef struct _IMAGE_NT_HEADERS
{ DWORD Signature; // PE文件標(biāo)識,為“PE\0\0”。依此識別NT文件頭是否有效 IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS,
為了編程方便,Windows為DOS文件標(biāo)記和PE文件標(biāo)記都定義了宏標(biāo)識。
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
檢查文件是否為PE文件的步驟如下:
(1)檢驗(yàn)文件頭部第一個字的值是否等于IMAGE_DOS_SIGNATURE,是則說明DOS MZ頭有效。
(2)一旦證明文件的DOS頭有效后,就可用e_lfanew來定位PE頭了。
(3)比較PE頭的第一個字是否等于IMAGE_NT_SIGNATURE。如果這個值也匹配,那么就認(rèn)為該文件是一個有效的PE文件。
下面是驗(yàn)證PE文件有效性的代碼,在配套光盤的08ValidPE工程下。
// ValidPE.cpp文件#include <afxdlgs.h>
#include "ValidPE.h"CMyApp theApp;BOOL CMyApp::InitInstance()
{// 彈出選色文件對話框CFileDialog dlg(TRUE);if(dlg.DoModal() != IDOK)return FALSE;// 打開檢查的文件HANDLE hFile = ::CreateFile(dlg.GetFileName(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if(hFile == INVALID_HANDLE_VALUE){MessageBox(NULL, "無效文件!", "ValidPE", MB_OK);}// 定義PE文件中的DOS頭和NT頭IMAGE_DOS_HEADER dosHeader;IMAGE_NT_HEADERS32 ntHeader; // 驗(yàn)證過程BOOL bValid = FALSE;DWORD dwRead;// 讀取DOS頭::ReadFile(hFile, &dosHeader, sizeof(dosHeader), &dwRead, NULL);if(dwRead == sizeof(dosHeader)){if(dosHeader.e_magic == IMAGE_DOS_SIGNATURE) // 是不是有效的DOS頭?{// 定位NT頭if(::SetFilePointer(hFile, dosHeader.e_lfanew, NULL, FILE_BEGIN) != -1){// 讀取NT頭::ReadFile(hFile, &ntHeader, sizeof(ntHeader), &dwRead, NULL);if(dwRead == sizeof(ntHeader)){if(ntHeader.Signature == IMAGE_NT_SIGNATURE) // 是不是有效的NT頭bValid = TRUE;}}}}// 顯示結(jié)果if(bValid)MessageBox(NULL, "是一個PE格式的文件!", "ValidPE", MB_OK);elseMessageBox(NULL, "不是一個PE格式的文件!", "ValidPE", MB_OK);::CloseHandle(hFile);return FALSE;
}
上述代碼簡單明確,先利用Windows定義的宏IMAGE_DOS_SIGNATURE判斷DOS頭,比較DOS頭的e_magic字段;再通過DOS頭的e_lfanew字段定位到NT頭;最后檢查NT頭的Signature字段是不是IMAGE_NT_SIGNATURE(即“PE\0\0”)。
8.1.5 MFC的支持(CFile類)
CFile是一個相當(dāng)簡單的封裝了一部分文件I/O處理函數(shù)的類。它的成員函數(shù)用于打開和關(guān)閉文件、讀寫文件數(shù)據(jù)、刪除和重命名文件、取得文件信息。它的公開成員變量m_hFile保存了與CFile對象關(guān)聯(lián)的文件的文件句柄。一個受保護(hù)的CString類型的成員變量m_strFileName保存了文件的名稱。成員函數(shù)GetFilePath、GetFileName和GetFileTitle能夠用來提取整個或者部分文件名。比如,如果完整的文件名是“C:\MyWork\File.txt”,GetFilePath返回整個字符串,GetFileName返回“File.txt”,GetFileTitle返回“File”。
但是詳述這些函數(shù)就會忽略CFile類的特色,這就是用來寫數(shù)據(jù)到磁盤和從磁盤讀數(shù)據(jù)的函數(shù)。下面簡單介紹CFile類用法。
**1.打開和創(chuàng)建文件 **
使用CFile類打開文件有兩種方法。
(1)構(gòu)造一個未初始化的CFile對象,調(diào)用CFile::Open函數(shù)。下面的部分代碼使用這個技術(shù)以讀寫權(quán)限打開一個名稱為File.txt的文件。
CFile file;
if(file.Open(_T ("File.txt"), CFile::modeReadWrite))
{ // 打開文件成功
}
CFile::Open函數(shù)的返回值是BOOL類型的變量。如果打開文件出錯,還想進(jìn)一步了解出錯的原因,可以創(chuàng)建一個CFileException對象,傳遞它的地址到Open函數(shù)的第3個參數(shù)。
CFile file;
CFileException e;
if (file.Open(_T ("File.txt"), CFile::modeReadWrite, &e))
{ // 打開文件成功
}
else
{ // 打開文件失敗,告訴用戶原因 e.ReportError();
}
如果打開失敗,CFile::Open函數(shù)會使用描述失敗原因的信息初始化一個CFileException對象。ReportError成員函數(shù)基于這個信息顯示一個出錯對話框??梢酝ㄟ^檢查CFileException類的公有成員m_cause找到導(dǎo)致這個錯誤的原因。
(2)使用CFile類的構(gòu)造函數(shù)??梢詫?chuàng)建文件對象和打開文件合并成一步,如下面代碼所示。
CFile file(_T ("File.txt"), CFile::modeReadWrite);
如果文件不能被打開,CFile的構(gòu)造函數(shù)會拋出一個CFileException異常。因此,使用CFile::CFile函數(shù)打開文件的代碼通常使用try和catch塊來捕獲錯誤。
try
{ CFile file(_T ("File.txt"), CFile::modeReadWrite);
}
catch(CFileException* e)
{ // 出錯了! e->ReportError(); e->Delete();
}
刪除MFC拋出的異常是程序?qū)懽髡叩呢?zé)任,所以在程序中處理完異常之后要調(diào)用異常對象的Delete函數(shù)。
為了創(chuàng)建一個文件而不是打開一個存在的文件,要在CFile::Open或者CFile構(gòu)造函數(shù)的第二個參數(shù)中包含上CFile::modeCreate標(biāo)記,如下代碼所示。
CFile file(_T("File.txt"), CFile::modeReadWrite | CFile::modeCreate);
如果以這種方式創(chuàng)建的文件存在,它的長度會被截為0。為了在文件不存在時(shí)創(chuàng)建它,存在的時(shí)候僅打開而不截去,應(yīng)再包含上CFile::modeNoTruncate標(biāo)記,如下面代碼所示。
CFile file(_T("File.txt"), CFile::modeReadWrite | CFile::modeCreate | CFile::modeNoTruncate);
默認(rèn)情況下,由CFile::Open或CFile::CFile打開的文件使用的是獨(dú)占模式,即CreateFile API中的第3個參數(shù)dwShareMode被設(shè)為了0。如果需要,在打開文件時(shí)也可以指定一個共享模式,以明確同意其他訪問此文件的操作。這里是4個可以選擇的共享模式:
CFile::shareDenyNone 不獨(dú)占這個文件
CFile::shareDenyRead 拒絕其他代碼對這個文件進(jìn)行讀操作
CFile::shareDenyWrite 拒絕其他代碼對這個文件進(jìn)行寫操作
CFile::shareExclusive 拒絕其他代碼對這個文件進(jìn)行讀和寫操作(默認(rèn))
另外,還可以指定下面3個對象訪問類型中的一個:
CFile::modeReadWrite 請求讀寫訪問
CFile::modeRead 僅請求讀訪問
CFile::modeWrite 僅請求寫訪問
常用的做法是允許其他程序以只讀方式打開文件,但是拒絕它們寫入數(shù)據(jù)。
CFile file(_T("File.txt"), CFile::modeReadWrite | CFile::modeCreate | CFile::modeNoTruncate);
如果在上面的代碼執(zhí)行之前,文件已經(jīng)以可寫的方式打開了,這個調(diào)用將會失敗,CFile類會拋出CFileException異常,異常對象的m_cause成員等于CFileException::sharingViolation。
CFile類的成員函數(shù)Close會調(diào)用CloseHandle API關(guān)閉應(yīng)用程序打開的文件對象句柄。如果句柄沒有關(guān)閉,類的析構(gòu)函數(shù)也會調(diào)用Close函數(shù)關(guān)閉它。顯式調(diào)用Close函數(shù)一般都是為了關(guān)閉當(dāng)前打開的文件,以便使用同樣的CFile對象打開另一個文件。
2.讀寫文件
CFile類中從文件中讀取數(shù)據(jù)的成員函數(shù)是Read。例如,下面的代碼申請了一塊4KB大小的文件I/O緩沖區(qū),每次從文件讀取4KB大小的數(shù)據(jù)。
BYTE buffer[4096];
CFile file (_T("File.txt"), CFile::modeRead);
DWORD dwBytesRemaining = file.GetLength();
while(dwBytesRemaining)
{ UINT nBytesRead = file.Read(buffer, sizeof(buffer)); dwBytesRemaining -= nBytesRead;
}
文件中未讀取的字節(jié)數(shù)保存在dwBytesRemaining變量里,此變量由CFile::GetLength返回的文件長度初始化。每次調(diào)用Read之后,從文件中讀取的字節(jié)數(shù)(nBytesRead)會從dwBytesRemaining變量里減去。直到dwBytesRemaining為0整個while循環(huán)才結(jié)束。
CFile類還提供了Write成員函數(shù)向文件寫入數(shù)據(jù),Seek成員函數(shù)移動文件指針,它們都和相關(guān)API一一對應(yīng)。可以通過跟蹤程序的執(zhí)行來查看這些函數(shù)的實(shí)現(xiàn)代碼。