【问题标题】:FindNextFile ERROR_INVALID_NAMEFindNextFile ERROR_INVALID_NAME
【发布时间】:2015-12-04 02:04:14
【问题描述】:

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

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

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

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

返回值

如果函数成功,则返回值非零,并且 lpFindFileData 参数包含有关找到的下一个文件或目录的信息。 如果函数失败,返回值为零,lpFindFileData 的内容是不确定的。要获取扩展错误信息,请调用 GetLastError 函数。 如果函数因找不到更多匹配文件而失败,GetLastError 函数将返回 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 始终为 true,并且 GetLastError 返回 ERROR_INVALID_NAME ...

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

【问题讨论】:

标签: c# winapi pinvoke


【解决方案1】:

仅调用Marshal.GetLastWin32Error 是API 调用报告失败。对于FindNextFile,它通过返回false 来实现。您正在不加选择地检查Marshal.GetLastWin32Error 返回的值。

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

我有点困惑,正如官方文档中所说,如果发生错误,它应该返回 0。

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

代码应该更像这样:

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 有什么问题?

【讨论】:

  • 1) 这可能是一个见仁见智的问题,您可以不同意,但是当我定义 ap/Invoke 签名时,我通常会明确说明我要调用哪个版本的函数(即- FindFirstFileW 而不是 FindFirstFile)。 2) CA1414 指定您应该使用MarshalAs 属性进行布尔返回。 3)你可能想以最后一段开头,因为这完全是一个学术练习。 4) 说了这么多,+1 写得很好。
  • @theB 所有有效点。谢谢你。我会让提问者在我的手机上阅读你的 cmets 而不是另一个编辑!
  • 感谢您的所有好建议和详细回答!这是真的,我不应该指定入口点......我希望我不必重新发明轮子。我使用了 GetFiles 以及您之前提到的方法。我一直在寻找异步等效项,但找不到。所以这就是为什么我最终得到了这个解决方案,老实说我也不喜欢。
  • 但是,当您说“但是您只是忽略了返回值并测试了 Marshal.GetLastWin32Error() 返回的值。”时,我不明白。对不起,我度过了一个艰难的夜晚,也许我错过了但对我来说,我确实测试了 FindNextFile 返回的值。否则对不起,如果我错了(我感到羞耻^^)顺便说一句,我以这种方式调用 GetLastWin32Error 来测试整体的行为。现在它可以工作了,我可以通过适当的错误处理来重写它。
  • 您确实测试了返回的值,但仅在调用 GetLastWin32Error 之后。如果 FindNextFile 成功,则最后一个错误没有意义。 FindFirstFile 也犯了同样的错误。顺便说一句,我们每天看到这个经典错误 > 10 次。
猜你喜欢
  • 1970-01-01
  • 2011-04-09
  • 1970-01-01
  • 2014-05-26
  • 2023-03-16
  • 1970-01-01
  • 2020-07-28
  • 1970-01-01
  • 2016-11-23
相关资源
最近更新 更多