[C# | Net1.1] 回到.Net1.1與NT - 在.Net1.1取得電腦現有硬碟的磁碟機代號與可用空間的方法-上透過Win32 API

  • 4107
  • 0
  • C#
  • 2015-02-18

在.Net1.1上並沒有DriveInfo這個類別存在,此類別是從Net2.0開始出現使用的,所以在.Net1.1上要得知目前電腦的硬碟有多少磁碟機代號,各個磁碟機的類型與是否有格式化過,這些狀況都無法方便的處理…

那麼到底我們要如何是好呢?

前言

 


 

在.Net1.1上並沒有DriveInfo這個類別存在,此類別是從Net2.0開始出現使用的,所以在.Net1.1上要得知目前電腦的硬碟有多少磁碟機代號,各個磁碟機的類型與是否有格式化過,這些狀況都無法方便的處理…

 

那麼到底我們要如何是好呢?

這也是此篇的記錄與用意,如果有朋友正好在使用.Net1.1也遇到此問題時,希望這篇文章解決你的困擾,那開始吧!

 

在.Net1.1上取得所有磁碟機

 


 

在.Net1.1上有一個方法叫做 GetLogicalDrives 是可以取得所有的磁碟機代號,但是是以字串的方式顯示:

//1.尋找電腦上的所有磁碟機,在System.IO下
string[] drivesStr = Directory.GetLogicalDrives();

 

但是此方法因為是字串所以我們還是無法辨識,到底這個磁碟機是可移除式隨身碟,還是CDROM,或是軟碟機,並且如果是一般的硬碟空間,那麼他到底是已經格式化可以用了,或是尚未格式化?

 

 

檢測磁碟代號字串的資訊狀況-使用Interop透過Win32 API 調用 GetDriveType Function

 


 

是的,在.Net1.1上有許多不存在的方法,於是乎除了自己寫以外,而在這個硬碟的部分,還有WMI可以使用,但是因為我是在NT上執行測試,所以NT內建沒有WMI這套服務,所以還要多多使用Win32 API去Interop一下,也真的要感謝至少Win32 API上想要的功能到目前為止,都有,而且可以Interop一下,不然很慘..

 

讓我們翻閱一下Win32 API的GetDriveType Function ,裡面有提到: Determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive.

 

所以,我們便可以透過Interop使用kernel32.dll中的GetDriveType,如下:

using System.Runtime.InteropServices;

/// <summary>
/// 尋找磁碟機的類型,透過c++ API取得
/// </summary>
/// <param name="nDrive">磁碟機</param>
/// <returns>回傳類型碼</returns>
[DllImport( "kernel32.dll", EntryPoint="GetDriveTypeA" )]
private static extern int CPlusPlusGetDriveType(string nDrive);

 

這邊稍為介紹一下,使用Interop的部分

[DllImport( "kernel32.dll", EntryPoint="GetDriveTypeA" )]  是一個對應到外部COM的方法,kernel32.dll是要使用的DLL檔案,EntryPoint是要對應的進入方法名稱,通常Win32 API文件會提供,包含了W與A分別表示Unicode 與 ANSI,是意味著,指定傳送字串參數至Win32 API時的格式,而一般C#與VB都是預設ASNI,所以是填A。

 

或是可以另外指定 CharSet = CharSet.Unicode ,而後在EntryPoint="GetDriveType" ,便會自動依據Unicode或是ANSI,對應至W或A,如下:

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetDriveType")]

 

而有時候,如果你的extern方法名稱與Win32 API的方法名稱一致,便不需要另外指定EntryPoint,除非如這邊的例子,我有改過對應到C#的名稱,才需要,如下:

 [DllImport( "kernel32.dll" )]
private static extern int GetDriveType(string nDrive);

 

回過頭來,所以會發現在看Win32 API的GetDriveType時,最下面有提到一行是:GetDriveTypeW (Unicode) and GetDriveTypeA (ANSI) ,而這邊,因為我的對應C#方法名稱改變,所以要寫EntryPoint,並指定A。

 

再來,由文件上寫,回傳的int值分別代表了不同磁碟機類型,所以我另外用一個enum包起來;

public enum DriveType
{
        DRIVE_UNKNOWN =0,
        DRIVE_NO_ROOT_DIR,
        DRIVE_REMOVABLE,
        DRIVE_FIXED,
        DRIVE_REMOTE,
        DRIVE_CDROM
}

 

其中,如果是一般的本機磁碟,這裡是指DRIVE_FIXED ,因此便可以得到哪幾台是本機磁碟。

但是,有沒有格式化,從這邊是看不出來的,如果是未格式化的本機磁碟,仍然會回傳 3,也就是DRIVE_FIXED ,所以下一部我們要處理這個問題,並顯示仍有多少可用空間。

 


判斷是否有格式化,與可用空間-使用Win32 API的GetDiskFreeSpaceEx function

 


 

再次翻閱Win32 API,發現了有GetDiskFreeSpaceEx function 可以使用,介紹如下:

Retrieves information about the amount of space that is available on a disk volume, which is the total amount of space, the total amount of free space, and the total amount of free space available to the user that is associated with the calling thread

 

可以取得剩餘可用空間,所有空間,所有可用空間等資訊。

呼叫方式如下:

/// <summary>
/// 尋找此硬碟機的可用空間
/// </summary>
/// <param name="lpDirectoryName">要尋找的印碟機,格式為 硬碟機代號:\\</param>
/// <param name="lpFreeBytesAvailable">回傳仍有多少可用Bytes</param>
/// <param name="lpTotalNumberOfBytes">回傳此硬碟機的所有空間Bytes</param>
/// <param name="lpTotalNumberOfFreeBytes"></param>
/// <returns></returns>
[DllImport("kernel32.dll",EntryPoint="GetDiskFreeSpaceExA" , SetLastError = true)]
public static extern bool CPlusPlusGetDiskFreeSpaceEx(string lpDirectoryName,
    out ulong lpFreeBytesAvailable,
    out ulong lpTotalNumberOfBytes,
    out ulong lpTotalNumberOfFreeBytes);

 

這邊傳入的第一個參數變式磁碟機的字串代號,後面三個out參數,便是取得硬碟目前空間狀態,而回傳值表示,是否在呼叫過程中成功或錯誤,而這個錯誤很重要,我們需要拿取他的錯誤資訊,因為這關係到我們的硬碟是否有格式化的判斷依據。

 

另外,還需要透過 SetLastError = true,取得錯誤後的資訊,將會以代碼來顯示。

現在來看一下呼叫使用CPlusPlusGetDiskFreeSpaceEx的方式:

if(type == DriveType.DRIVE_FIXED){
           ulong freeBytesAvail;
           ulong totalNumOfBytes;
           ulong totalNumOfFreeBytes;
           //判斷有無資料空間,錯誤表示可能尚未格式化,不尋找
           if (!CPlusPlusGetDiskFreeSpaceEx(driveStr, out freeBytesAvail, out totalNumOfBytes, out totalNumOfFreeBytes))
           {
               int win32ErrorCode = Marshal.GetLastWin32Error();
               Console.Error.WriteLine("Error occurred code: {0} ",win32ErrorCode.ToString());
               if(win32ErrorCode == 1005)
                    Console.Error.WriteLine("Error Meesage: ERROR_UNRECOGNIZED_VOLUME");
           }

}

 

上面的程式碼,在執行時,如果本機磁碟正處於未格式化,便會進到判斷內,而此時由於我們有設定 SetLastError = true ,便能透過Interop的Marshal.GetLastWin32Error() 取得錯誤碼。

在來從文件上比對錯誤碼的訊息,這裡會看到,如果是1005即表示 ERROR_UNRECOGNIZED_VOLUME ,在System Erro Codes中的錯誤,以此方式去判斷因為尚未格式化,所以無法對Volume有認知(說心裡話,這也是我目前唯一可以辨認的方法..如果有更好的解法,歡迎跟我分享@@)。

 

最後附上一個判斷的結圖:

DriveInfo in Net1.1

 

最後,此方法已經在Windows 2000與NT上使用Work ok.

 

 

補充-Win32 API GetDiskFreeSpaceEx 與GetDiskFreeSpace function的差異

 


 

此部分在官方文章中有提到解說,主要是早期的作業系統95/98才會有影響,98後的系統都是可以使用GetDiskFreeSpaceEx ,如下是官方資訊:

一般而言,Win32 程式應該使用 GetDiskFreeSpaceEx 來決定一個磁碟區及呼叫端可以使用的可用空間數量的總大小。這不可能的其中一種情況時,程式會在零售版 Windows 95 (組建 950.6) 上執行,因為 GetDiskFreeSpaceEx 以後開始引進以 OEM 服務版本 2 (OSR2) 中的 Windows 95。


如果您的程式可以在零售版 Windows 95 上執行,您不應該直接呼叫 GetDiskFreeSpaceEx 因為零售版 Windows 95 中不會實作這個函式,並直接呼叫將會防止您的程式載入。相反地,您應該以動態方式連結至它透過 GetProcAddress。如果傳回的指標為非 NULL,然後您的應用程式可以安全地呼叫 GetDiskFreeSpaceEx 透過指標 ;如果傳回的指標為 NULL,您應該還原成 GetDiskFreeSpace。在本文稍後的程式碼範例示範如何執行這項操作。

 

參考資料

[C#]使用 DriveInfo 類別取得磁碟資訊

Directory.GetLogicalDrives Method

GetDriveType Function

DllImportAttribute.EntryPoint 欄位

DllImportAttribute.CharSet 欄位

GetDiskFreeSpaceEx function

System Error Codes

http://www.java2s.com/Tutorial/CSharp/0520__Windows/Getfreediskspace.htm

了解,並使用 GetDiskFreeSpace 和 GetDiskFreeSpaceEx

 


 

文章中的敘述如有觀念不正確錯誤的部分,歡迎告知指正 謝謝 =)

另外要轉載請附上出處 感謝