查找下一个文件 ERROR_INVALID_NAME

FindNextFile ERROR_INVALID_NAME

我正在尝试使用 WINAPI 函数 FindFirstFileFindNextFile。 但是,我遇到了一些问题。

当我第一次调用 FindFirstFile 函数时,它工作正常。我有一个有效的处理程序,第一个 folder/file 名称已正确填充到 WIN32_FIND_DATA 结构中。 GetLastError 未发现错误。

然后,我调用 FindNextFile return 是真的,因为我正在扫描的目录中有更多文件夹。但是我无法检索下一个 folder/file 名称和 GetLastError returns 123 (0x7B) ERROR_INVALID_NAME。 我有点困惑,因为它在官方文档中说,如果发生错误,它应该 return 0.

https://msdn.microsoft.com/en-us/library/windows/desktop/aa364428(v=vs.85).aspx

Return value

If the function succeeds, the return value is nonzero and the lpFindFileData parameter contains information about the next file or directory found. If the function fails, the return value is zero and the contents of lpFindFileData are indeterminate. To get extended error information, call the GetLastError function. If the function fails because no more matching files can be found, the GetLastError function returns ERROR_NO_MORE_FILES.

我在 Windows 7 x64 上使用 .NET 4.5.1 和 Visual Studio 2013。 这是示例代码。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WIN32_FIND_DATA
{
    public uint dwFileAttributes;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
    public uint nFileSizeHigh;
    public uint nFileSizeLow;
    public uint dwReserved0;
    public uint dwReserved1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string cFileName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
    public string cAlternateFileName;
}

...

    [DllImport("Kernel32.dll", EntryPoint = "FindFirstFile", SetLastError = true)]
    public static extern IntPtr FindFirstFile(string lpFileName, ref WIN32_FIND_DATA lpFindFileData);

    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("Kernel32.dll", EntryPoint = "FindFirstFile", SetLastError = true)]
    public static extern bool FindNextFile(IntPtr hFindFile, ref WIN32_FIND_DATA lpFindFileData);

    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("Kernel32.dll", EntryPoint = "FindClose", SetLastError = true)]
    public static extern bool FindClose(IntPtr hFindFile);

...

public static void Test()
{
    WIN32_FIND_DATA metaDataFile = new WIN32_FIND_DATA();

    IntPtr nextHandle = FileScanner.FindFirstFile("C:\*", ref metaDataFile);
    Console.WriteLine(Marshal.GetLastWin32Error()); // This equals 0x0 ERROR_SUCCESS
    Console.WriteLine(metaDataFile.cFileName); // This equals $Recycle.Bin

    /* Check invalid handler */
    if (nextHandle != new IntPtr(-1L))
    {
        bool moreFiles = true;
        while (moreFiles)
        {
            moreFiles = FileScanner.FindNextFile(nextHandle, ref metaDataFile);
            Console.WriteLine(Marshal.GetLastWin32Error()); // This equals 0x7B ERROR_INVALID_NAME
            Console.WriteLine(metaDataFile.cFileName); // This equals $Recycle.Bin and this value never change. 
        }
    
    FindClose(nextHandle);
    }
}

出于某种原因,moreFiles 始终为真,GetLastError returns ERROR_INVALID_NAME ...

如果您需要任何详细信息,请问我。 任何帮助将不胜感激!

只有调用Marshal.GetLastWin32Error是API调用失败。在 FindNextFile 的情况下,它通过 returning false 来实现。您正在不加区别地检查由 Marshal.GetLastWin32Error 编辑的值 return。

文档在告诉您函数如何指示失败时清楚地说明了这一点。你甚至链接了文本。但是你说:

I'm a little confused as it says in the official documentation that if an error occurs it should return 0.

没错。因此,请对照 0 检查 return 值。在 BOOL 编组为 C# bool 的情况下,这意味着如果函数 returns false 失败。但是您只是忽略了 return 值并测试了由 Marshal.GetLastWin32Error() 编辑的值 return,完全不同。

代码应该更像这样:

public static void Test()
{
    WIN32_FIND_DATA fd = new WIN32_FIND_DATA();

    IntPtr findHandle = FileScanner.FindFirstFile("C:\*", ref fd);
    if (findHandle == INVALID_HANDLE_VALUE)
        throw new Win32Exception();

    do
    {
        Console.WriteLine(fd.cFileName);
    } while (FileScanner.FindNextFile(findHandle, ref fd));

    // you might check that Marshal.GetLastWin32Error() returns ERROR_NO_MORE_FILES
    // at this point, otherwise the enumeration failed abnormally

    if (!FindClose(findHandle))
        throw new Win32Exception();
}

您的另一个问题是您的 p/invoke 声明,这是对您伤害最大的问题。仔细看这个:

[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("Kernel32.dll", EntryPoint = "FindFirstFile", SetLastError = true)]
public static extern bool FindNextFile(IntPtr hFindFile,
    ref WIN32_FIND_DATA lpFindFileData);

EntryPoint 不正确。所以你实际上是在调用 FindFirstFile 而不是 FindNextFile 并且失败也就不足为奇了。

在不需要时指定 EntryPoint 简直是自找麻烦。而你已经掉进了陷阱。我会这样声明这些导入:

[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindFirstFile(string lpFileName,
    ref WIN32_FIND_DATA lpFindFileData);

[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool FindNextFile(IntPtr hFindFile,
    ref WIN32_FIND_DATA lpFindFileData);

[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool FindClose(IntPtr hFindFile);

请注意,不需要 return 属性,因为 UnmanagedType.Bool 是默认值。

然后您需要将结构上的 CharSet 更改为 CharSet.Unicode 以匹配。在这里选择 ANSI 毫无意义。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WIN32_FIND_DATA
{
    ....
}

最后,所有这些代码在我看来都毫无意义。 Directory.EnumerateFilesDirectory.EnumerateDirectories 有什么问题?