问题
两台机器显示运行时错误:IO Exception The device is not ready
。这只发生在我测试过的8台机器中的2台。
这些是从SBS 2003到Windows XP,Vista,7,8,8.1,Server 2012的服务器版本。它的范围很广。
有问题的两台机器是:
请注意:我在虚拟机上安装了Windows XP,安装了.NET framework 4.0,程序运行正常。
我的调查和测试
开始我的应用程序的目标是.NET framework 4.0,所有引用的外部.DLLS都包含在应用程序启动文件夹中。
从研究中我确定错误与驱动器访问有关。在我的应用程序中有两个实例,我专门查询设备的系统驱动器。一旦抓住磁盘空间而另一个抓住串口。
所以我创建了两个程序,一个具有我用来获取磁盘空间的功能,另一个具有我用来获取HDD Serial的功能。
我在工作机器上运行了两个程序,然后看到一个消息框显示有可用磁盘空间和HDD串行(这并不奇怪。)
我在显示IO错误的上述机器上尝试过,我收到(对于这两个应用程序) programname.exe is not a valid Win32 application.
^这很奇怪吧?
以下是有关的两个功能。
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并运行了!虽然有例外,请参阅下面的粘贴箱。
答案 0 :(得分:4)
由于这些是旧机器,因此有几件事情可以发挥作用。我有一台XP机器(用于Civ3),它也失败了,其中一个调用的IO异常。我为另一个推断。
对于VolumeSerial,它首先尝试WMI,然后在API失败时轮询API。对于Freespace,我删除了所有VisualBasic调用,并将其替换为对DriveInfo
的NET调用,然后在NET无法传递的情况下调用GetDiskFreeSpaceEx
。
守则(我建议所有这一类):
进口
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Text
NativeMethods:
<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 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)
处的旧软盘驱动器。代码没有检查它们是否是FixedDrives,也没有检查该驱动器是否准备就绪。这是此需要DriveLetter的部分原因。您可以轻松更改它以获取第一个固定磁盘或Windows驱动器的VolumeSerial,具体取决于它的用途。
四个程序有点过分,2 API版本应该始终有效,但由于我们讨论的是相当旧的系统,故障保护看起来并不是一个坏主意。 另外,正如我所说,我只能重现其中一个错误,所以一个多管齐下的解决方案似乎是谨慎的。