FindNextFile ERROR_INVALID_NAME

时间:2015-09-08 11:42:39

标签: c# winapi pinvoke

我尝试使用WINAPI函数 FindFirstFile FindNextFile 。 但是,我遇到了一些问题。

当我第一次调用函数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 ......

如果您需要任何细节,请问我。 真的很感激任何帮助!

1 个答案:

答案 0 :(得分:3)

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