硬盘序列号(Serial Number)不等于卷标号(Volume Name),后者虽然很容易得到,但是格式化分区后就会重写,不可靠。遗憾的是很多朋友往往分不清这一点。
要得到硬盘的物理序列号,可以通过WMI,也就是Win32_PhysicalMedia.SerialNumber。可惜的是Windows 98/ME的WMI并不支持这个类,访问时会出现异常。
受陆麟的例子的启发,我们还可以通过S.M.A.R.T.接口,直接从RING3调用API DeviceIoControl()来获取硬盘信息,而不需要写VXD或者DRIVER。这样这个问题就解决了,我对它进行了封装,大量使用了P/Invoke技术,一个完整的Library。支持Windows 98-2003。
使用上很简单:
HardDiskInfo hdd = AtapiDevice.GetHddInfo(0); // 第一个硬盘Console.WriteLine("Module Number: {0}", hdd.ModuleNumber);Console.WriteLine("Serial Number: {0}", hdd.SerialNumber);Console.WriteLine("Firmware: {0}", hdd.Firmware);Console.WriteLine("Capacity: {0} M", hdd.Capacity);
下面是全部代码:
using System;using System.Runtime.InteropServices;using System.Text;
namespace Sunmast.Hardware{[Serializable]public struct HardDiskInfo{///
#region Internal Structs
[StructLayout(LayoutKind.Sequential, Pack=1)]internal struct GetVersionOutParams{public byte bVersion;public byte bRevision;public byte bReserved;public byte bIDEDeviceMap;public uint fCapabilities;[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]public uint[] dwReserved; // For future use.}
[StructLayout(LayoutKind.Sequential, Pack=1)]internal struct IdeRegs{public byte bFeaturesReg;public byte bSectorCountReg;public byte bSectorNumberReg;public byte bCylLowReg;public byte bCylHighReg;public byte bDriveHeadReg;public byte bCommandReg;public byte bReserved;}
[StructLayout(LayoutKind.Sequential, Pack=1)]internal struct SendCmdInParams{public uint cBufferSize;public IdeRegs irDriveRegs;public byte bDriveNumber;[MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]public byte[] bReserved;[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]public uint[] dwReserved;public byte bBuffer;}
[StructLayout(LayoutKind.Sequential, Pack=1)]internal struct DriverStatus{public byte bDriverError;public byte bIDEStatus;[MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]public byte[] bReserved;[MarshalAs(UnmanagedType.ByValArray, SizeConst=2)]public uint[] dwReserved;}
[StructLayout(LayoutKind.Sequential, Pack=1)]internal struct SendCmdOutParams{public uint cBufferSize;public DriverStatus DriverStatus;public IdSector bBuffer;}
[StructLayout(LayoutKind.Sequential, Pack=1, Size=512)]internal struct IdSector{public ushort wGenConfig;public ushort wNumCyls;public ushort wReserved;public ushort wNumHeads;public ushort wBytesPerTrack;public ushort wBytesPerSector;public ushort wSectorsPerTrack;[MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]public ushort[] wVendorUnique;[MarshalAs(UnmanagedType.ByValArray, SizeConst=20)]public byte[] sSerialNumber;public ushort wBufferType;public ushort wBufferSize;public ushort wECCSize;[MarshalAs(UnmanagedType.ByValArray, SizeConst=8)]public byte[] sFirmwareRev;[MarshalAs(UnmanagedType.ByValArray, SizeConst=40)]public byte[] sModelNumber;public ushort wMoreVendorUnique;public ushort wDoubleWordIO;public ushort wCapabilities;public ushort wReserved1;public ushort wPIOTiming;public ushort wDMATiming;public ushort wBS;public ushort wNumCurrentCyls;public ushort wNumCurrentHeads;public ushort wNumCurrentSectorsPerTrack;public uint ulCurrentSectorCapacity;public ushort wMultSectorStuff;public uint ulTotalAddressableSectors;public ushort wSingleWordDMA;public ushort wMultiWordDMA;[MarshalAs(UnmanagedType.ByValArray, SizeConst=128)]public byte[] bReserved;}
#endregion
///
#region DllImport
[DllImport("kernel32.dll", SetLastError=true)]static extern int CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError=true)]static extern IntPtr CreateFile(string lpFileName,uint dwDesiredAccess,uint dwShareMode,IntPtr lpSecurityAttributes,uint dwCreationDisposition,uint dwFlagsAndAttributes,IntPtr hTemplateFile);
[DllImport("kernel32.dll")]static extern int DeviceIoControl(IntPtr hDevice,uint dwIoControlCode,IntPtr lpInBuffer,uint nInBufferSize,ref GetVersionOutParams lpOutBuffer,uint nOutBufferSize,ref uint lPBytesReturned,[Out] IntPtr lpOverlapped);
[DllImport("kernel32.dll")]static extern int DeviceIoControl(IntPtr hDevice,uint dwIoControlCode,ref SendCmdInParams lpInBuffer,uint nInBufferSize,ref SendCmdOutParams lpOutBuffer,uint nOutBufferSize,ref uint lpBytesReturned,[Out] IntPtr lpOverlapped);
const uint DFP_GET_VERSION = 0x00074080;const uint DFP_SEND_DRIVE_COMMAND = 0x0007c084;const uint DFP_RECEIVE_DRIVE_DATA = 0x0007c088;
const uint GENERIC_READ = 0x80000000;const uint GENERIC_WRITE = 0x40000000;const uint FILE_SHARE_READ = 0x00000001;const uint FILE_SHARE_WRITE = 0x00000002;const uint CREATE_NEW = 1;const uint OPEN_EXISTING = 3;
#endregion
#region GetHddInfo
///
#region GetHddInfo9x
private static HardDiskInfo GetHddInfo9x(byte driveIndex){GetVersionOutParams vers = new GetVersionOutParams();SendCmdInParams inParam = new SendCmdInParams();SendCmdOutParams outParam = new SendCmdOutParams();uint bytesReturned = 0;
IntPtr hDevice = CreateFile(@"\\.\Smartvsd",0,0,IntPtr.Zero,CREATE_NEW,0,IntPtr.Zero);if (hDevice == IntPtr.Zero){throw new Exception("Open smartvsd.vxd failed.");}if (0 == DeviceIoControl(hDevice,DFP_GET_VERSION,IntPtr.Zero,0,ref vers,(uint)Marshal.SizeOf(vers),ref bytesReturned,IntPtr.Zero)){CloseHandle(hDevice);throw new Exception("DeviceIoControl failed:DFP_GET_VERSION");}// If IDE identify command not supported, failsif (0 == (vers.fCapabilities & 1)){CloseHandle(hDevice);throw new Exception("Error: IDE identify command not supported.");}if (0 != (driveIndex & 1)){inParam.irDriveRegs.bDriveHeadReg = 0xb0;}else{inParam.irDriveRegs.bDriveHeadReg = 0xa0;}if (0 != (vers.fCapabilities & (16 >> driveIndex))){// We don''t detect a ATAPI device.CloseHandle(hDevice);throw new Exception(string.Format("Drive {0} is a ATAPI device, we don''t detect it",driveIndex + 1));}else{inParam.irDriveRegs.bCommandReg = 0xec;}inParam.bDriveNumber = driveIndex;inParam.irDriveRegs.bSectorCountReg = 1;inParam.irDriveRegs.bSectorNumberReg = 1;inParam.cBufferSize = 512;if (0 == DeviceIoControl(hDevice,DFP_RECEIVE_DRIVE_DATA,ref inParam,(uint)Marshal.SizeOf(inParam),ref outParam,(uint)Marshal.SizeOf(outParam),ref bytesReturned,IntPtr.Zero)){CloseHandle(hDevice);throw new Exception("DeviceIoControl failed: DFP_RECEIVE_DRIVE_DATA");}CloseHandle(hDevice);
return GetHardDiskInfo(outParam.bBuffer);}
#endregion
#region GetHddInfoNT
private static HardDiskInfo GetHddInfoNT(byte driveIndex){GetVersionOutParams vers = new GetVersionOutParams();SendCmdInParams inParam = new SendCmdInParams();SendCmdOutParams outParam = new SendCmdOutParams();uint bytesReturned = 0;
// We start in NT/Win2000IntPtr hDevice = CreateFile(string.Format(@"\\.\PhysicalDrive{0}",driveIndex),GENERIC_READGENERIC_WRITE,FILE_SHARE_READFILE_SHARE_WRITE,IntPtr.Zero,OPEN_EXISTING,0,IntPtr.Zero);if (hDevice == IntPtr.Zero){throw new Exception("CreateFile faild.");}if (0 == DeviceIoControl(hDevice,DFP_GET_VERSION,IntPtr.Zero,0,ref vers,(uint)Marshal.SizeOf(vers),ref bytesReturned,IntPtr.Zero)){CloseHandle(hDevice);throw new Exception(string.Format("Drive {0} may not exists.",driveIndex + 1));}// If IDE identify command not supported, failsif (0 == (vers.fCapabilities & 1)){CloseHandle(hDevice);throw new Exception("Error: IDE identify command not supported.");}// Identify the IDE drivesif (0 != (driveIndex & 1)){inParam.irDriveRegs.bDriveHeadReg = 0xb0;}else{inParam.irDriveRegs.bDriveHeadReg=0xa0;}if (0 != (vers.fCapabilities & (16 >> driveIndex))){// We don''t detect a ATAPI device.CloseHandle(hDevice);throw new Exception(string.Format("Drive {0} is a ATAPI device, we don''t detect it.",driveIndex + 1));}else{inParam.irDriveRegs.bCommandReg = 0xec;}inParam.bDriveNumber = driveIndex;inParam.irDriveRegs.bSectorCountReg = 1;inParam.irDriveRegs.bSectorNumberReg = 1;inParam.cBufferSize = 512;
if (0 == DeviceIoControl(hDevice,DFP_RECEIVE_DRIVE_DATA,ref inParam,(uint)Marshal.SizeOf(inParam),ref outParam,(uint)Marshal.SizeOf(outParam),ref bytesReturned,IntPtr.Zero)){CloseHandle(hDevice);throw new Exception("DeviceIoControl failed: DFP_RECEIVE_DRIVE_DATA");}CloseHandle(hDevice);
return GetHardDiskInfo(outParam.bBuffer);}
#endregion
private static HardDiskInfo GetHardDiskInfo(IdSector phdinfo){HardDiskInfo hddInfo = new HardDiskInfo();
ChangeByteOrder(phdinfo.sModelNumber);hddInfo.ModuleNumber = Encoding.ASCII.GetString(phdinfo.sModelNumber).Trim();
ChangeByteOrder(phdinfo.sFirmwareRev);hddInfo.Firmware = Encoding.ASCII.GetString(phdinfo.sFirmwareRev).Trim();
ChangeByteOrder(phdinfo.sSerialNumber);hddInfo.SerialNumber = Encoding.ASCII.GetString(phdinfo.sSerialNumber).Trim();
hddInfo.Capacity = phdinfo.ulTotalAddressableSectors / 2 / 1024;
return hddInfo;}
private static void ChangeByteOrder(byte[] charArray){byte temp;for(int i = 0; i < charArray.Length; i += 2){temp = charArray[i];charArray[i] = charArray[i+1];charArray[i+1] = temp;}}
#endregion}}
注:
在Windows 98/ME中,S.M.A.R.T并不缺省安装,请将SMARTVSD.VXD拷贝到%SYSTEM%\IOSUBSYS目录下。在Windows 2000/2003下,需要Administrators组的权限。不要在装有SCSI硬盘的机器上尝试了,因为SCSI硬盘根本不存在序列号。
最终版权归陆麟所有,任何人不得将此代码占为己有。