排雷Windows CreateFile、ReadFile、WriteFile API

【經歷了一次編寫FAT16讀寫磁盤的作業,對三個文件操作的API有一些體會,如果MSDN和其他博客沒有解決你的問題,不妨試試這篇】

環境Win7,VS2015

CreateFile

CreateFile在各種地方都有很完全的說明,只不過我要做的事情稍微小衆一些。我希望打開一個磁盤,而不是一個文件或者IO口之類。以下是在其他博客也有提到,經過無數次檢驗的代碼:

fat16 = CreateFile("\\\\.\\I:",
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		0,
		NULL);	//打開驅動器

特殊之處就在於那個所謂lpFileName參數。MSDN上提到了需要使用\\?\前綴等事宜,但並沒有實例。我的理解是前面的\\是所謂的UNC前綴,後面的\\.\是Win32 Device Namespace的前綴,最後是一般的磁盤名\I:。另外我操作的是一個U盤,如果要對自己電腦上的磁盤操作,應該以管理員身份運行,否則報5號錯誤(拒絕訪問)。

文件和設備命名的說明

正常讀寫就填GENERIC_READ|GENERIC_WRITE即可,據說FILE_SHARE_READ | FILE_SHARE_WRITE是共享讀寫參數,然而我的程序調用WriteFile時還是不能同時用winHex查看I盤,原因後述。

 

ReadFile

ReadFile這個函數真是呵呵,比fread fget之流難用太多了。然而由於是CreateFile拿到的HANDLE句柄,不知道怎麼用C的庫函數操作,只好在WinAPI這棵樹上吊死。

一般使用讀磁盤的需求肯定是想讀任意位置,任意長度的數據。然而在ReadFile這裏不 存 在 的。如果使用同步方式讀磁盤,一次只能讀一個扇區,且只能從扇區起始位置開始。ReadFile的lpOverlapped參數不是有Offset成員嗎?這個是異步讀寫才“起作用”的參數。想用也行,在CreateFile時加入異步讀寫的FLAG,接着讀出的數據位置是對了,但總有那麼幾字節丟失。可能是異步讀寫時緩衝區被一邊讀一邊寫導致的?不是還有SetFilePointer函數嗎?它也只能移動整數個扇區。。

下面對上面一段話詳細說明。以下是讀第base個扇區的內容放進buffer裏的代碼。

int sizeOfSector;//扇區大小
DWORD lpdwBytesRead = 0;
OVERLAPPED over = { 0 };
unsigned char buffer[sizeOfSector+1];//存放讀出內容的數組(實際請填數字相信不用我說也知道)
over.Offset;//讀位置的偏移量,扇區大小的整數倍
ReadFile(*p_fat16, buffer, sizeOfSector, &lpdwBytesRead, &over);

第三個參數就是讀出內容的大小,可爲sizeOfSector的整數倍,如果不是整數倍則超出部分按sizeOfSector算。這就需要你將buffer設置得足夠大,否則會Duang地溢出。第四個參數最好填一個指針而不是NULL或者0。第五個參數就是上面提到的OVERLAPPED類型的參數。可能讀者已經發現了矛盾,不是說要在CreateFile里加入一個FILE_FALG_OVERLAPPED參數才能讓這個over結構體有效嗎?但事實是,以上的代碼實現了base*sizeOfSector字節的偏移,而且沒有缺失的數據。不過也僅僅能偏移sizeOfSector的整數倍,如果需要讀非base*sizeOfSector處的數據,另在buffer裏偏移即可。

而且你不加這個LPOVERLAPPED指針還不行,置NULL會導致內存訪問衝突wtf。

下面是前面提到的異步操作的CreateFile形式

fat16 = CreateFile("\\\\.\\I:",
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_FALG_OVERLAPPED,
		NULL);

 

WriteFile

int sizeOfSector;//扇區大小
DWORD base;//寫入位置的偏移量,扇區大小的整數倍
DWORD lpdwBytesRead = 0;
VOID* alterSec;//指向寫入內容的指針
SetFilePointer(*p_fat16, base, NULL, FILE_BEGIN);
WriteFile(*p_fat16, alterSec, sizeOfSector, &lpdwBytesRead, NULL);

 經過ReadFile的折騰,已經能平靜面對WriteFile的詭奇無端了。

首先是SetFilePointer函數又好使了,作用是將文件指針(即寫入的起始位置)偏移扇區大小的整數倍個字節。得虧是好使了,不然作業沒法做了。

然後請看WriteFile最後那個NULL,那裏的參數類型是LPOVERLAPPED。爲什麼要用SetFilePointer而不是像ReadFile一樣用over.Offset呢?顯然是OVERLAPPED在這裏不好使啊,很正常,接受。

最大的一個問題是,如果直接這樣運行,很可能會報5號拒絕訪問錯誤。需要在CreateFile後“鎖定磁盤”,代碼:

BOOL bLOCK = DeviceIoControl(
		fat16,
		FSCTL_LOCK_VOLUME,
		NULL,
		0,
		NULL,
		0,
		&dwByteReturned,
		NULL);

 正是這個操作使得Create時的FILE_SHARE_READ | FILE_SHARE_WRITE失效了。而且如果運行時磁盤被佔用,例如被winHex查看,也是會報錯的。

 

 

【以上是非專業人員使用這三個函數時遇到的問題和他的土味解決方法,很可能存在更正常,主流,漂亮,體面的解決方法,不過管他呢,作業做完完事。】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章