常有開發者詢問如何在 VB.NET 中取得硬碟序號。最簡單的方式是透過 WMI 類別,但這實際上取得的是「磁碟區序號」,而非硬碟製造商燒錄的實體序號。磁碟區序號在格式化時產生,且無需重新格式化即可變更,雖然實務上較少這樣做。
本文嘗試使用 Windows 原生 API GetVolumeInformation 來達成同樣目的,這需要在 C# 中使用 P/Invoke。這項技術對軟體授權機制非常實用:例如在安裝時讀取使用者 C 槽的磁碟區序號,透過網路註冊模組送至 Web Service,作為加密金鑰產生合法授權碼,寫入登錄表。若嘗試將單機授權產品安裝至另一台電腦,金鑰將失效。
呼叫非受控 API 時,參數型別必須精確對應。GetVolumeInformation 的簽章如下:
[DllImport("kernel32.dll")]
private static extern long GetVolumeInformation(
string PathName,
StringBuilder VolumeNameBuffer,
UInt32 VolumeNameSize,
ref UInt32 VolumeSerialNumber,
ref UInt32 MaximumComponentLength,
ref UInt32 FileSystemFlags,
StringBuilder FileSystemNameBuffer,
UInt32 FileSystemNameSize
);
注意:部分直覺上應為 int 的參數,實際必須用 UInt32(對應 .NET 的 uint);字串參數則常需以 StringBuilder 傳遞。
以下封裝方法回傳字串格式的序號,方便直接使用:
public string GetVolumeSerial(string strDriveLetter)
{
uint serNum = 0;
uint maxCompLen = 0;
StringBuilder volLabel = new StringBuilder(256); // 磁碟標籤
UInt32 volFlags = new UInt32();
StringBuilder fsName = new StringBuilder(256); // 檔案系統名稱
strDriveLetter += ":\\"; // 補全路徑格式,例如 "C:\\"
long ret = GetVolumeInformation(
strDriveLetter,
volLabel,
(UInt32)volLabel.Capacity,
ref serNum,
ref maxCompLen,
ref volFlags,
fsName,
(UInt32)fsName.Capacity
);
return Convert.ToString(serNum);
}