You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用DeviceIoControl时为何出现文件访问错误?

问题描述

在Windows系统中递归遍历文件夹结构,获取每个文件和文件夹的唯一文件系统标识符。针对文件夹,使用WinAPI的CreateFile()和DeviceIoControl()函数获取ID,但部分有效路径(可在资源管理器浏览,System.IO.Path.Exists()确认存在且可访问)调用DeviceIoControl()时返回错误码2(ERROR_FILE_NOT_FOUND)。已排除权限问题,且错误为“文件未找到”而非“路径未找到”(错误码3),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)

问题分析与解决

DeviceIoControl()返回ERROR_FILE_NOT_FOUND的核心原因

你使用的FSCTL_GET_OBJECT_ID控制码存在严格的适用限制,以下是关键敏感因素:

  1. 文件系统兼容性限制
    FSCTL_GET_OBJECT_ID仅支持NTFS文件系统。如果目标文件夹位于FAT32、exFAT等非NTFS分区,即使CreateFile()成功打开文件夹,DeviceIoControl()也会返回ERROR_FILE_NOT_FOUND——这些文件系统根本不支持Object ID特性。

  2. Object ID未被系统分配
    NTFS不会自动为所有文件夹分配Object ID,仅当系统或特定应用(如索引服务、备份软件)需要时才会生成。从未被此类功能访问过的文件夹没有Object ID,此时调用FSCTL_GET_OBJECT_ID会触发文件未找到错误。

  3. 结构封送的潜在问题
    你的FILE_OBJECTID_BUFFER注释掉了ExtendedInfo字段,导致Marshal.SizeOf(buffer)计算的缓冲区大小不足。完整的FILE_OBJECTID_BUFFER应为64字节,缺失字段会引发缓冲区不匹配,间接导致错误。

  4. 特殊路径场景
    如果路径指向符号链接或 junction点,CreateFile()打开的是链接本身而非目标文件夹,FSCTL_GET_OBJECT_ID无法识别这种间接对象,从而返回错误。

修正方案

  • 前置检查文件系统:调用前通过DriveInfo.DriveFormat判断分区是否为NTFS,跳过不支持的分区。
  • 改用通用ID获取方式:使用GetFileInformationByHandle()返回的FileIndexHigh+FileIndexLow组合作为标识符,该方式适用于所有Windows支持的文件系统,无需依赖Object ID。
  • 修复结构定义:恢复FILE_OBJECTID_BUFFER中的ExtendedInfo字段,确保缓冲区大小正确。
  • 处理特殊路径:对符号链接/junction点,调用GetFinalPathNameByHandle()解析实际路径后再操作。

内容的提问来源于stack exchange,提问作者J Collins

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 16:57:31