【經歷了一次編寫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查看,也是會報錯的。