我正在尝试使用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 ...

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

最佳答案

仅调用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怎么了?

09-06 03:14