今天在除錯一個Winform程式,使用File.Exists 判斷一個已經存在的驅動檔案,程式一直返回false。因為驅動檔案屬於系統目錄,心想難道是許可權不夠導致的?然後用管理員身份執行軟體,依然返回false。嚇的我趕緊去系統目錄C:\Windows\system32\drives 搜尋該檔案,一看在這裡啊,怎麼還是返回false。開始還以為VS出問題了,然後嘗試判斷一個D盤下的檔案,發現可以判斷成功。
判斷程式碼如下
// 獲取系統目錄
var system = Environment.GetFolderPath(Environment.SpecialFolder.System);
var filePath = system + @"\drivers\evserial7.sys";
var flag = File.Exists(filePath);
MessageBox.Show("系統路徑:" + filePath + "\r\n" + "checkDrives:" + flag);
執行結果
在系統中找檔案確實存在,如下圖
奇怪在於當我把程式設定成Release執行的時候判斷又成功了,第一張圖Debug執行的,下圖是Release
當時心想為什麼Release和Debug不一樣呢,一時有點詫異,然後就想著,看看反編譯後的IL程式碼,看兩者是不是有什麼差別?
Debug反編譯
Release反編譯
這樣看兩者並沒有任何差異,因為我們判斷是路徑,所以我們只看IL中路徑是否有不同編譯, 兩者中IL_0002: call string [mscorlib]System.Environment::GetFolderPath(valuetype [mscorlib]System.Environment/SpecialFolder)都是一樣的,說明系統路徑一樣,後面拼接字串更加不會有任何差別。
最終通過stackoverflow找到原因,因為我當前程式編譯的是32位即X86,32位應用程式在64位系統中是無法訪問system32目錄的。為什麼我程式要選擇編譯32位呢,因為我程式當中需要呼叫一個C++寫的dll,該dll是32位的,我無法改變。如果我程式編譯時選擇AnyCPU或者X64,那麼該dll是無法呼叫的。所以我只能改成X86。
為什麼當我用Release又能判斷正確,原因在於上圖的這個生成配置頁面在Release的時候目標平臺任然是:Any CPU。(不是X86),所以能判斷成功。當把Release模式的目標平臺改為X86後結果就是flase了。
那麼編譯的32位程式到底該怎麼判斷64位系統中的系統檔案呢?其實當我們32位應用程式訪問system32資料夾的時候,64位系統會自動幫我們自動重定向到SysWoW64資料夾。通過專業解釋該資料夾主要是被設計用來處理許多在32-bit Windows和64-bit Windows之間的不同的問題,使得可以在64-bit Windows中執行32-bit程式。
所以我們在32位程式的時候判斷系統路徑其實已經重定向到了: C:\Windows\SysWoW64\drivers\evserial7.sys,這是系統自動重定向,所以IL程式碼中我們也看不到差異。這個目錄肯定不存我們的檔案,所以導致返回判斷false。
那我們如何在32位下真正的訪問system32目錄呢?不要系統重定向。使用 C:\Windows\SysNative路徑,這是個虛擬路徑,我們在Windows資源管理器中是無法找到的。但是他最終還是會指向到system32中。SysNative資料夾目的就是讓32位應用程式訪問64位系統檔案的方法。
現在我將程式碼改下,前面的 Environment.GetFolderPath(Environment.SpecialFolder.System)是獲取system32這裡要改為Environment.SpecialFolder.Windows,獲取windows目錄,並在下面拼接上Sysnative目錄。
這只是在判斷系統檔案的時候會存在32位和64位的差異,普通檔案就不存在任何影響了。
最後修改程式碼如下
// 獲取windows目錄
var system = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
var filePath = system + @"\Sysnative\drivers\evserial7.sys";
var flag = File.Exists(filePath);
MessageBox.Show("系統路徑:" + filePath + "\r\n" + "checkDrives:" + flag);
執行結果