问题
两台机器显示运行时错误:IO Exception The device is not read
。我测试过的 8 台机器中只有 2 台会出现这种情况。
这些服务器版本从 SBS 2003 到 Windows XP、Vista、7、8、8.1、Server 2012。范围很广。
有问题的两台机器是:
- Windows Server 2003 SP2(均安装.NET Framework V4/V4.0)
- Windows XP SP3 (已安装.NET Framework V4/V4.0)
请注意:我在虚拟机上全新安装了 Windows XP,安装了 .NET Framework 4.0,程序运行时没有错误。
我的调查和测试
要启动我的应用程序,目标是 .NET Framework 4.0,所有引用的外部 .DLL 都包含在应用程序启动文件夹中。
根据研究,我确定该错误与驱动器访问有关。在我的应用程序中,有两个实例专门查询设备的系统驱动器。一次抢磁盘空间,另一次抢串口。
所以我创建了两个程序,一个具有我用来获取磁盘空间的功能,另一个具有我用来获取HDD串行的功能。
我在工作机器上运行了这两个程序,然后看到一个消息框显示,其中包含可用磁盘空间和 HDD 序列(这并不奇怪。)
我在上述显示 IO 错误的计算机上进行了尝试,并且收到了(对于两个应用程序) programname.exe 不是有效的 Win32 应用程序。
^ 这很奇怪吧?
这是有问题的两个函数。
Public Shared Function getHardwareID() As String
Dim drive As String = "C"
Dim disk As ManagementObject = _
New ManagementObject _
("win32_logicaldisk.deviceid=""" + drive + ":""")
disk.Get()
Return disk("VolumeSerialNumber").ToString()
End Function
Public Shared Function getFreeDiskSpace() As String
Dim freespacekb = My.Computer.FileSystem.Drives.Item(0).AvailableFreeSpace.ToString
freespacekb = Format(freespacekb / 1024 / 1024 / 1024, "#0.00") _
& " GB Free"
Return freespacekb.ToString
End Function
是的,“C”是两台机器的驱动器号。
编辑:
我将 IO 测试之一定位到 .NET Framework 4.0 Client Profile,并且它运行了!尽管有异常(exception),请参阅下面的粘贴箱。
最佳答案
由于这些都是旧机器,因此可能有几个因素在起作用。我有一台 XP 机器(适用于 Civ3),它也因其中一个调用出现 IO 异常而失败。我推断了另一个。
对于 VolumeSerial,它首先尝试 WMI,如果失败则轮询 API。对于 Freespace,我摆脱了所有 VisualBasic 调用,并将其替换为对 DriveInfo
的 NET 调用,然后在 NET 无法交付时调用 GetDiskFreeSpaceEx
。
代码(我建议为所有这些创建一个类):
导入
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Text
native 方法:
<DllImport("Kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function GetVolumeInformation(ByVal RootPathName As String,
ByVal VolumeNameBuffer As System.Text.StringBuilder,
ByVal VolumeNameSize As UInt32,
ByRef VolumeSerialNumber As UInt32,
ByRef MaximumComponentLength As UInt32,
ByRef FileSystemFlags As UInt32, _
ByVal FileSystemNameBuffer As System.Text.StringBuilder,
ByVal FileSystemNameSize As UInt32
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetDiskFreeSpaceEx(lpDirectoryName As String,
ByRef lpFreeBytesAvailable As ULong,
ByRef lpTotalNumberOfBytes As ULong,
ByRef lpTotalNumberOfFreeBytes As ULong
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<Flags>
Private Enum FileSystemFeature As UInteger
CaseSensitiveSearch = 1
CasePreservedNames = 2
UnicodeOnDisk = 4
PersistentACLS = 8
FileCompression = &H10
VolumeQuotas = &H20
SupportsSparseFiles = &H40
SupportsReparsePoints = &H80
VolumeIsCompressed = &H8000
SupportsObjectIDs = &H10000
SupportsEncryption = &H20000
NamedStreams = &H40000
ReadOnlyVolume = &H80000
SequentialWriteOnce = &H100000
SupportsTransactions = &H200000
End Enum
如果 DriveSpace 的两种选择均失败,则返回 -1,这是一个明显非法的值。我这样做而不是抛出异常。 格式化返回值实际上应该与获取返回值分开,这将使评估返回值更容易,但我将其保留为您的方式。
我将调用更改为需要驱动器盘符才能进行轮询,以帮助消除一些错误,例如尝试轮询不存在的驱动器。 WMI 版本可以轻松修改为在第一个固定磁盘甚至“Windows”驱动器上工作,具体取决于其用途。
Public Shared Function getFreeDiskSpace(drvName As String) As String
' since this fails on just some machines
' AND the machines mentioned are old, they may well have floppy drives
' failure signal
Dim freespace As Double = -1
Dim myFreeBytesAvailable As ULong
Dim myTotalNumberOfBytes As ULong
Dim myTotalNumberOfFreeBytes As ULong
If String.IsNullOrEmpty(drvName) Then
Return freespace.ToString
End If
' try the NET method
Dim di As DriveInfo() = DriveInfo.GetDrives
Try
For Each drv In di
' look for desired drive in list
If drv.Name.ToLowerInvariant.StartsWith(drvName.ToLowerInvariant(0)) Then
' apply your desired logic...
' check only fixed drives for this use?
' at least check if they are ready
'If drv.DriveType = DriveType.Fixed
' If drv.IsReady
' min test
If drv.DriveType = drv.IsReady Then
freespace = drv.TotalFreeSpace
End If
Exit For
End If
Next
Catch ex As Exception
End Try
' no answer yet, ask the API; should always work on valid drives
If freespace = -1 Then
' format drv letter to name
Dim drvpath As String = String.Format("{0}:\", drvName.ToLowerInvariant(0))
' call API
If GetDiskFreeSpaceEx(drvpath, myFreeBytesAvailable,
myTotalNumberOfBytes, myTotalNumberOfFreeBytes) Then
freespace = myFreeBytesAvailable
End If
End If
' format bytes
If freespace > -1 Then
freespace = freespace / 1024 / 1024 / 1024
End If
' ret string
Return freespace.ToString("#00.00 Gb Free")
End Function
卷系列分为 2 个进程。主要公开的一个:
' again, call with drive letter "C"
Public Shared Function getHardwareID(drvLtr As String) As String
Dim volSerial As String = ""
Dim drive As String = (drvLtr.ToLower)(0)
Try
Dim disk As ManagementObject = _
New ManagementObject("win32_logicaldisk.deviceid=""" + drive + ":""")
disk.Get()
volSerial = disk("VolumeSerialNumber").ToString()
Catch ex As Exception
' call private API version for help
volSerial = GetVolumeSerialFromAPI(drive)
Finally
End Try
Return volSerial
End Function
WMI 无法完成工作时的私有(private)助手:
Private Shared Function GetVolumeSerialFromAPI(driveLetter As String) As String
' format drive letter
Dim myDrvLtr As String = (driveLetter(0) & ":\").ToLower
' allocate space
Dim volname As New StringBuilder(261)
Dim fsName As New StringBuilder(261)
Dim sernum, maxlen As UInt32
' out_Flags
Dim flags As FileSystemFeature
If GetVolumeInformation(myDrvLtr, volname, volname.Capacity,
sernum, maxlen, flags,
fsName, fsName.Capacity) Then
' format to Hex like WMI
Return sernum.ToString("X")
Else
Return ""
End If
End Function
我怀疑根本问题是 Drives.Item(0)
处的旧软盘驱动器。该代码不会检查它们是否是固定驱动器,也不会检查该驱动器是否准备就绪。这是需要驱动器盘符的部分原因。您可以轻松更改它以获取第一个固定磁盘或 Windows 驱动器的 VolumeSerial,具体取决于其用途。
四个过程有点过分了,2 API 版本应该总是可以工作,但由于我们谈论的是相当旧的系统,所以故障保护似乎不是一个坏主意。 另外,正如我所说,我只能重现其中一个错误,因此多管齐下的解决方案似乎是谨慎的。
关于vb.net - 某些 XP 系统获取 WMI 磁盘信息时出现 IO 异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24809487/