【问题标题】:How to list files in a directory using the Windows API?如何使用 Windows API 列出目录中的文件?
【发布时间】:2017-05-15 06:07:14
【问题描述】:

我有这段代码,它会显示包含目录本身而不是其内容的文件夹。我想显示它的内容。我不想使用 boost::filesystem。

我该如何解决这个问题?

代码:

#include <windows.h>
#include <iostream>

int main()
{
    WIN32_FIND_DATA data;
    HANDLE hFind = FindFirstFile("C:\\semester2", &data);      // DIRECTORY

    if ( hFind != INVALID_HANDLE_VALUE ) {
        do {
            std::cout << data.cFileName << std::endl;
        } while (FindNextFile(hFind, &data));
        FindClose(hFind);
    }
}

输出:

semester2

【问题讨论】:

  • @RawN 好吧,不是真的,但它给了我一个想法!现在它正在工作!谢谢! :)
  • 旁注:您使用的是窄字符文字和编译时选择的字符宽度 API。现代构建环境倾向于默认为 Unicode 构建,这将导致此问题。我强烈建议在任何地方使用明确的 Unicode 类型和 API(将 W 后缀添加到 WIN32_FIND_DATAFindFirstFileFindNextFileL 前缀到路径文字,将 std::cout 替换为 std::wcout)或始终使用TCHARs(添加#include &lt;tchar.h&gt;,使字符串文字_T("C:\\semester2"),并有条件地将std::tcout别名为std::cout/std::wcout视情况而定)。

标签: c++ windows winapi directory-listing


【解决方案1】:
HANDLE hFind = FindFirstFile("C:\\semester2", &data);       // DIRECTORY

你得到了目录,因为那是你所要求的。如果您想要这些文件,请索取:

HANDLE hFind = FindFirstFile("C:\\semester2\\*", &data);  // FILES

(如果您愿意,您可以改用*.*,但显然这只适用于向后兼容性黑客,因此应该避免使用。请参阅 cmets 和 RbMm 的答案。)

【讨论】:

  • 太棒了!谢谢!一个问题,为什么在显示文件之前有点(。)? 我已经用您的更改编辑了我的问题的输出 :)
  • 每个目录(根目录除外)都包含指向自身. 的链接和指向其父目录.. 的链接,如果您不想包含这些内容,可以轻松跳过它们。
  • 明白。怎么能跳过?
  • 没什么复杂的。通常只有几个字符串比较和一个if 语句。
  • *.* 只是回到 DOS。在我看来,改用* 会更清楚。
【解决方案2】:

Harrys 的回答实际上会在您想要的文件夹"C:\\semester2" 中生成具有扩展名的文件和文件夹。

例如,如果您有一个名为"C:\\semester2\\math.course" 的文件夹,上面的示例也可以找到它。此外,如果您有一个名为 "C:\\semester2\\math_scores" 的文件(请注意它没有扩展名),它将不会被找到。

考虑到上述情况,我建议以下解决方案:

HANDLE hFind = FindFirstFile("C:\\semester2\\*", &data); 

这将列出目录下的整个项目列表。 可以通过以下方式过滤目录:

if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// directory
}
else
{
// file
}

以下可用于参考:FileAttributes constantsFIND_DATA structFindFirstFile API

【讨论】:

  • 这不是真的。 *.* 将查找没有扩展名的文件。有关详细信息,请参阅 RbMm 的答案。
【解决方案3】:

让我对"*.*""*" 做一些笔记。这些申报者不相等。

我们的文件夹中可以存在 2 个不同的文件:somefilesomefile.

如果我们使用低级 api ZwQueryDirectoryFile"*.*" 作为搜索表达式(这是第 10 个参数 - FileName [in, optional]) - 我们只会得到 somefile.。但是如果我们使用"*",我们会得到两个文件——somefilesomefile.

如果我们尝试FindFirstFile("C:\\semester2\\*.*", &amp;data);,我们可以注意到somefilesomefile. 两个文件都被返回。所以这里"*.*""*" 的效果是一样的——用法上没有区别。

为什么会这样?因为在 kernelbase (kernel32) 中的 FindFirstFileEx 内部会特别检查 "*.*" 掩码,如果它为真 - 替换为 "" (与 "*" 具有相同效果的空名称)。

我认为这样做是为了解决当用户传递 "*.*" 而不是正确的 "*" 时的一个非常常见的错误,以及与旧代码的向后兼容性。

... 实际上并不是目录的一部分,因为它存储在 磁盘,但由 Win32 API 添加。

这不是真的。

  • 对于FAT 风格的文件系统,这实际上是作为第2 个条目存储在FAT 目录中的。
  • NTFS 中没有这样的条目,但是NTFS.sys 人为地添加了这两个条目,如果它们在掩码中。

所以这不是在 Win32 API 级别,而是在内核 - 驱动程序级别。

总而言之,"*.*" 可以在 Win32 API 上正常工作,现在最低限度 - 但正确和干净的方法是在此处使用 "*"
"*.*" 将与 ZwQueryDirectoryFile api 错误。

【讨论】:

  • *.* 支持是一种向后兼容功能,短文件名总是有扩展名。 (扩展名可能是空的,但它就在那里。)关于点和双点是内核的一部分而不是 Win32 是正确的,我观察到使用它们的长路径在命令行中不起作用(例如,制表符补全不起作用)但仔细观察似乎与cmd.exe 的工作方式有关。例如,如果将带点的长路径传递到记事本,则可以使用。
  • @HarryJohnston - 是的,我快速查看FindFirstFileEx 并注意它对"*.*" 进行特殊检查并将其修复为"" - 对于向后兼容性黑客来说,速度更快。但是我之前对此并不熟悉,因为在自己的代码中始终只使用ZwQueryDirectoryFile,而这里"*.*" 不返回somefile 之类的文件(但返回somefile.
  • 如果我指出这是人们建议不要不必要地使用本机 API 的原因之一,希望您能原谅我。 :-)
  • @HarryJohnston - 请原谅我在这里提到了 NT api。对于 99% 以上的用户模式程序员来说,这似乎是下流的脏话。但这里只是为了展示在哪个级别系统为"*.*" 破解。关于 NT api,我有另一种看法(我认为它不做这样的黑客攻击是正确的),我喜欢它直接返回错误代码(不转换它而失去准确性),严格统一的签名(比较 win32),很多更强大和更有效的比较win32,因为在win32中根本不存在这种可能性..但这一切在这里已经是题外话了。我不建议任何人使用这个:)
  • @HarryJohnston - 无论如何,谢谢。对不起,我的英语和母语 api :)
【解决方案4】:

这是一个示例实现:

#include <iostream>
#include <vector>
#include <string>
#include <Windows.h>

std::vector<std::string>
list_directory(
    const std::string &directory)
{
    WIN32_FIND_DATAA findData;
    HANDLE hFind = INVALID_HANDLE_VALUE;
    std::string full_path = directory + "\\*";
    std::vector<std::string> dir_list;

    hFind = FindFirstFileA(full_path.c_str(), &findData);

    if (hFind == INVALID_HANDLE_VALUE)
        throw std::runtime_error("Invalid handle value! Please check your path...");

    while (FindNextFileA(hFind, &findData) != 0)
    {
        dir_list.push_back(std::string(findData.cFileName));
    }

    FindClose(hFind);

    return dir_list;
}

注意:如果您使用 C++ 11,最好使用 boost::filesystem 或 std::filesystem 如果您使用 C++ 17。 此外,输入路径必须像 C:\path 而不是 C:\path\ 否则这将不起作用!!

【讨论】:

  • std::filesystem::path 自 C++ 17 起可用,这可能比 boost 更可取。
  • 我知道,但有些人仍在使用 C++ 11
猜你喜欢
  • 2011-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-29
  • 2010-11-14
  • 2023-03-13
  • 2011-04-15
相关资源
最近更新 更多