我正在 Windows 中递归文件夹结构,获取每个文件和文件夹的唯一文件系统标识符。对于每个文件夹,我使用
CreateFile()
和 DeviceIoControl()
WinAPI 函数来访问文件夹 ID,根据此问题的答案:
但是,对于某些路径(不是全部),尽管能够在资源管理器中浏览它们,甚至
System.IO.Path.Exists()
断言它们存在并且可访问,DeviceIoControl()
返回错误代码 2,ERROR_FILE_NOT_FOUND
。
最后的测试似乎确认这不是文件夹权限的问题,但有趣的是,该错误专门与未找到文件有关,而不是与未找到路径有关(错误代码为 3,
ERROR_PATH_NOT_FOUND
)。我已确认 System.IO.File.Exists()
正确拒绝所有文件夹,即使 DeviceIoControl()
确实访问了路径并返回有意义的值。
DeviceIoControl()
具体对什么敏感,为什么会在有效路径上绊倒?
Windows 10
Visual Studio Express 2022
.NET 8.0
WinAPI 调用:
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Public Class WinAPI
Public Shared GENERIC_READ As Integer = &H80000000
Public Shared FILE_FLAG_BACKUP_SEMANTICS As Integer = &H2000000
Public Shared OPEN_EXISTING As Integer = &H3
<StructLayout(LayoutKind.Sequential)>
Public Structure FILE_OBJECTID_BUFFER
Public Structure Union
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)>
Public BirthVolumeID() As Byte
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)>
Public BirthObjectId As Byte()
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)>
Public DomainID As Byte()
End Structure
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)>
Public ObjectID As Byte()
Public BirthInfo As Union
'<MarshalAs(UnmanagedType.ByValArray, SizeConst:=48)>
'Public ExtendedInfo As Byte()
End Structure
Public Structure FILETIME
Public dwLowDateTime As UInteger
Public dwHighDateTime As UInteger
End Structure
<StructLayout(LayoutKind.Sequential)>
Public Structure BY_HANDLE_FILE_INFORMATION
Public FileAttributes As UInteger
Public CreationTime As FILETIME
Public LastAccessTime As FILETIME
Public LastWriteTime As FILETIME
Public VolumeSerialNumber As UInteger
Public FileSizeHigh As UInteger
Public FileSizeLow As UInteger
Public NumberOfLinks As UInteger
Public FileIndexHigh As UInteger
Public FileIndexLow As UInteger
End Structure
<DllImport("kernel32.dll", SetLastError:=True)>
Public Shared Function DeviceIoControl(
hDevice As SafeFileHandle,
dwIoControlCode As UInteger,
lpInBuffer As IntPtr,
nInBufferSize As UInteger,
lpOutBuffer As IntPtr,
nOutBufferSize As Integer,
ByRef lpBytesReturned As UInteger,
lpOverlapped As IntPtr
) As Boolean
End Function
<DllImport("kernel32.dll", SetLastError:=True)>
Public Shared Function CreateFile(
fileName As String,
dwDesiredAccess As Integer,
dwShareMode As System.IO.FileShare,
securityAttrs_MustBeZero As IntPtr,
dwCreationDisposition As System.IO.FileMode,
dwFlagsAndAttributes As Integer,
hTemplateFile_MustBeZero As IntPtr
) As SafeFileHandle
End Function
<DllImport("kernel32.dll", SetLastError:=True)>
Public Shared Function GetFileInformationByHandle(
hFile As IntPtr,
ByRef lpFileInformation As BY_HANDLE_FILE_INFORMATION) As Boolean
End Function
End Class
实现功能:
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
Public Class FileAccess
Shared FSCTL_GET_OBJECT_ID As UInteger = &H9009C
Public Shared Function GetFileId(path As String) As UInt128
Using fs = IO.File.Open(
path,
IO.FileMode.OpenOrCreate,
IO.FileAccess.ReadWrite,
IO.FileShare.ReadWrite)
Dim info As WinAPI.BY_HANDLE_FILE_INFORMATION
WinAPI.GetFileInformationByHandle(fs.Handle, info)
Dim fileID As UInt128 = info.FileIndexHigh
fileID <<= 32
fileID = fileID Or info.FileIndexLow
Return fileID
'Return String.Format("{0:x}", ((info.FileIndexHigh << 32) Or info.FileIndexLow))
End Using
End Function
Public Shared Function GetFolderID(path As String) As UInt128
Dim result As UInt128
Dim buffer As WinAPI.FILE_OBJECTID_BUFFER = GetFolderIdBuffer(path)
result = BitConverter.GetUint128(buffer.ObjectID)
Return result
End Function
Private Shared Function GetFolderIdBuffer(path As String) As WinAPI.FILE_OBJECTID_BUFFER
Using hFile As SafeFileHandle = WinAPI.CreateFile(
path,
WinAPI.GENERIC_READ, IO.FileShare.Read,
IntPtr.Zero,
WinAPI.OPEN_EXISTING,
WinAPI.FILE_FLAG_BACKUP_SEMANTICS,
IntPtr.Zero
)
If hFile Is Nothing Or hFile.IsInvalid Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error())
Dim buffer As New WinAPI.FILE_OBJECTID_BUFFER
Dim nOutBufferSize As Integer = Marshal.SizeOf(buffer)
Dim lpOutBuffer As IntPtr = Marshal.AllocHGlobal(nOutBufferSize)
Dim lpBytesReturned As UInteger
Dim result As Boolean =
WinAPI.DeviceIoControl(
hFile, FSCTL_GET_OBJECT_ID,
IntPtr.Zero, 0,
lpOutBuffer, nOutBufferSize,
lpBytesReturned,
IntPtr.Zero
)
If Not result Then Throw New System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error())
Dim type As Type = GetType(WinAPI.FILE_OBJECTID_BUFFER)
buffer = Marshal.PtrToStructure(lpOutBuffer, type)
Marshal.FreeHGlobal(lpOutBuffer)
Return buffer
End Using
End Function
End Class
食用方法:
Dim folderID As UInt128 = FileAccess.GetFolderID(path)
FSCTL_GET_OBJECT_ID
文档
如果没有与指定句柄关联的对象标识符,则不会创建任何对象标识符并返回错误。
它没有说 which 错误,但 ERROR_FILE_NOT_FOUND 可能是匹配的。
接下来说
要创建对象标识符,请使用
。 要检索现有对象标识符或在一步中没有现有对象标识符时生成对象标识符,请使用FSCTL_SET_OBJECT_ID
。FSCTL_CREATE_OR_GET_OBJECT_ID
为您提供解决方案。
注意:
DeviceIoControl
没有单一的语义,因为它为您提供了一种方法来分派到不同驱动程序中的大量不同功能(您的情况可能是对 NTFS 文件系统驱动程序的调用),由 IOCTL 代码标识,每个不同的代码都有不同的前置条件和后置条件。