VB.NET USB插拔检测代码触发System.NullReferenceException异常求解
问题成因
- 代码未对
WM_DEVICECHANGE消息做类型过滤:Windows发送该消息时涵盖所有类型设备的变更事件(包括USB键鼠、集线器、打印机等非存储设备),这类事件的LParam要么是空指针,要么指向非卷类型的设备广播结构体,直接按DEV_BROADCAST_VOLUME类型解析必然触发空引用或内存读取错误。 - 自定义的
DEV_BROADCAST_VOLUME结构体未声明内存布局规则,和Windows API实际使用的结构体内存对齐规则不匹配,Marshal解析时会读取错误的内存位置。 - 代码中存在无任何处理逻辑的空Catch块,所有异常被直接吞掉,导致调试时无法快速定位错误触发的具体上下文。
- 盘符解析函数逻辑存在缺陷:通过直接等于
2^i判断盘符掩码的写法,无法适配多分区设备多位置位的场景,返回空字符后调用ToString()也存在触发空引用的风险。
可行修复方案
1. 修正结构体定义与常量声明
给结构体添加正确的内存布局特性,补充缺失的设备类型判定常量:
' 卷设备类型标识常量 Private Const DBT_DEVTYP_VOLUME As Integer = &H2 <Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential, CharSet:=Runtime.InteropServices.CharSet.Auto)> Private Structure DEV_BROADCAST_VOLUME Public dbcv_size As Int32 Public dbcv_devicetype As Int32 Public dbcv_reserved As Int32 Public dbcv_unitmask As Int32 Public dbcv_flags As Int16 End Structure
2. 重写WndProc消息处理逻辑
增加多层校验,仅处理存储卷的接入、拔出事件,确认参数合法后再解析结构体:
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) If m.Msg = WM_DEVICECHANGE Then Dim eventType As Integer = m.WParam.ToInt32() ' 仅过滤需要处理的设备接入、移除完成事件 If eventType = WM_DEVICECHANGE_WPPARAMS.DBT_DEVICEARRIVAL OrElse eventType = WM_DEVICECHANGE_WPPARAMS.DBT_DEVICEREMOVECOMPLETE Then ' 先校验LParam非空指针 If m.LParam <> IntPtr.Zero Then ' 先读取设备类型字段(结构体偏移4字节位置),确认是卷设备再做完整解析 Dim devType As Integer = Runtime.InteropServices.Marshal.ReadInt32(m.LParam, 4) If devType = DBT_DEVTYP_VOLUME Then Dim volumeInfo As DEV_BROADCAST_VOLUME = CType( Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, GetType(DEV_BROADCAST_VOLUME)), DEV_BROADCAST_VOLUME ) Dim driveLetter As Char = GetDriveLetterFromMask(volumeInfo.dbcv_unitmask) If driveLetter <> ChrW(0) Then Dim driveRootPath As String = driveLetter & ":\" Select Case eventType Case WM_DEVICECHANGE_WPPARAMS.DBT_DEVICEARRIVAL LblUSB.Text = driveRootPath GetFiles() MsgBox("USB已连接") Case WM_DEVICECHANGE_WPPARAMS.DBT_DEVICEREMOVECOMPLETE LblUSB.Text = String.Empty CheckedListBox1.Items.Clear() MsgBox("USB已拔出") End Select End If End If End If End If End If MyBase.WndProc(m) End Sub
3. 修正盘符解析函数逻辑
改用位与运算判断盘符掩码位,适配多分区设备场景,避免返回无效空值:
Private Function GetDriveLetterFromMask(ByVal unitMask As Int32) As Char For i As Integer = 0 To 25 ' 位与运算判断对应盘符位是否置位 If (unitMask And (1 << i)) <> 0 Then Return ChrW(AscW("A"c) + i) End If Next ' 无效掩码返回空字符标识 Return ChrW(0) End Function
额外注意事项
- 删除代码中所有无处理逻辑的空Catch块,至少添加异常日志输出逻辑,禁止直接吞掉所有异常,否则后续出现其他问题时完全无法定位。
- 修复后的逻辑只会响应挂载为逻辑盘符的USB存储设备,不会响应非存储类USB设备的变更事件,从根源上避免无效解析导致的空引用异常。
内容的提问来源于stack exchange,提问作者Giovanni De Miccoli
相关产品推荐
相关产品推荐

