[vb.net] 根据逻辑分区的盘符获取物理磁盘号


  在制作U盘锁程序的时候,需要通过逻辑分区获取对应的物理磁盘信息,以实现硬件绑定验证的效果,但搜了一下,好像都没有通过.net实现的例子,有些大佬说可以通过WMI实现,我尝试了New ManagementClass("Win32_DiskDrive")获取的参数,但都没有获取成功(也不排除是我太菜了)。

  但如果这个问题无法解决,U盘锁的安全性又会大打折扣,于是尝试使用“物理分区”“逻辑分区”之类的关键词进行搜索,终于找到了一篇C++实现逻辑分区获取物理分区的文章(Windows的磁盘操作之四——根据逻辑分区号获得物理磁盘号),简单的看了下,他是通过这样的方法实现的:

1、使用CreateFile打开逻辑分区,获取句柄

2、使用DeviceIoControl获取物理分区号

  随后,百度得到了CreateFile的声明,但DeviceIoControl的声明查不到vb版的,于是自己动手翻译,查阅了C++声明中的几种变量的类型,然后逐一翻译,最终得到的结果如下:

Declare Function DeviceIoControl Lib "kernel32" Alias "DeviceIoControl" (ByVal hDevice As IntPtr, ByVal dwIoControlCode As Integer, ByRef lpInBuffer As Object, ByVal nInBufferSize As Integer, ByRef lpOutBuffer As StorageDeviceNumber, ByVal nOutBufferSize As Integer, ByRef lpBytesReturned As Integer, ByVal lpOverlapped As Integer) As Integer

  在那篇文章中对于结构体变量STORAGE_DEVICE_NUMBER的描述是这样的:

结构体STORAGE_DEVICE_NUMBER定义为 typedef struct _STORAGE_DEVICE_NUMBER { DEVICE_TYPE  DeviceType; ULONG  DeviceNumber; ULONG  PartitionNumber; } STORAGE_DEVICE_NUMBER, *PSTORAGE_DEVICE_NUMBER;
  查了下DEVICE_TYPE类型的声明,发现他是DWORD类型的,也就是unsigned long,即和下面两种变量为同一类型,我便将结构体定义成这样:
Structure StorageDeviceNumber
    Public DeviceType As ULong
    Public DeviceNumber As ULong
    Public PartitionNumber As ULong
End Structure

  但是获取了好多次,都不能获取到正确的物理磁盘号,他的代码在VC++下是能够正常跑的,所以应该是我翻译的过程有问题。最后经过层层排查,最后发现问题出在了结构体变量的定义上,于是乎去求助了下万能的度娘,在知道上看到原来C++的long类型的取值范围是-2^31~2^31,这与vb.net中Long类型的取值范围并不对应这是vb.net中Integer的取值范围)!

  变量在内存中占用的长度都不相同了,难道结果还能正确吗?所以说这个结构体变量中的ULong类型应当改为UInteger或UInt32,改完,测试,终于获取到了正确的物理分区号。当然,最后千万别忘了使用CloseHandle销毁句柄,否则会一直保持占用状态哦。

  经过整理,最终代码如下:

 1 Declare Function DeviceIoControl Lib "kernel32" Alias "DeviceIoControl" (ByVal hDevice As IntPtr, ByVal dwIoControlCode As Integer, ByRef lpInBuffer As Object, ByVal nInBufferSize As Integer, ByRef lpOutBuffer As StorageDeviceNumber, ByVal nOutBufferSize As Integer, ByRef lpBytesReturned As Integer, ByVal lpOverlapped As Integer) As Integer
 2 
 3 Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Integer, ByVal dwShareMode As Integer, ByVal lpSecurityAttributes As IntPtr, ByVal dwCreationDisposition As Integer, ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As Integer) As Integer
 4 
 5 Declare Function CloseHandle Lib "kernel32" Alias "CloseHandle" (ByVal hObject As IntPtr) As Integer
 6 
 7 Structure StorageDeviceNumber
 8     Public DeviceType As UInteger
 9     Public DeviceNumber As UInteger
10     Public PartitionNumber As UInteger
11 End Structure
12 
13 ''' 
14 ''' 从逻辑分区获取物理硬盘
15 ''' 
16 ''' 逻辑分区号
17 ''' 
18 ''' 
19 Function GetPhysicalDisk(ByVal DriveId As Char) As UInteger
20     Dim DHandle As IntPtr = CreateFile("\\.\" & DriveId & ":", 4 Or 8, 1 Or 2, Nothing, 3, 0, Nothing)
21     If DHandle = -1 Then Throw New Exception("错误 - 获取句柄失败")
22     Dim DiskInfo As New StorageDeviceNumber
23     Dim ReturnedByteLength As Integer = 0
24     Dim Result As Boolean = DeviceIoControl(DHandle, &H2D1080, 0, 0, DiskInfo, 122, ReturnedByteLength, Nothing)
25     CloseHandle(DHandle)
26     If Not Result Then Throw New Exception("错误 - 获取物理磁盘号失败")
27     If ReturnedByteLength = 0 Then Throw New Exception("错误 - 返回值为空")
28     Return DiskInfo.DeviceNumber
29 End Function