我尝试使用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 ......
如果您需要任何细节,请问我。 真的很感激任何帮助!
答案 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.EnumerateFiles
和Directory.EnumerateDirectories
有什么问题?