我正在尝试使用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.EnumerateFiles
和Directory.EnumerateDirectories
怎么了?