如何从文件 HANDLE 获取到包含目录的 HANDLE?

cha*_*m15 1 c c++ windows file-io winapi

给定文件的句柄(例如C:\\FolderA\\file.txt),我想要一个函数,它将句柄返回到包含目录(在前面的示例中,它将是一个句柄到C:\\FolderA)。例如:

HANDLE hFile = CreateFileA(
                  "C:\\FolderA\\file.txt",
                  GENERIC_READ,
                  FILE_SHARE_READ,
                  NULL,
                  OPEN_EXISTING,
                  FILE_ATTRIBUTE_NORMAL,
                  NULL);
HANDLE hDirectory = somefunc(hFile);
Run Code Online (Sandbox Code Playgroud)

可能的实现someFunc

HANDLE someFunc(HANDLE h)
{
    char *path = getPath(h);             // "C:\\FolderA\\file.txt"
    char *parent = getParentPath(path);  // "C:\\FolderA"
    HANDLE hFile = CreateFileA(
              parent,
              GENERIC_READ,
              FILE_SHARE_READ,
              NULL,
              OPEN_EXISTING,
              FILE_ATTRIBUTE_NORMAL,
              NULL);
    free(parent);
    free(path);
    return hFile;
}
Run Code Online (Sandbox Code Playgroud)

但是有没有一种方法可以someFuncgetParentPath不查看字符串并删除最后一个目录分隔符之后的所有内容的情况下实现(因为从性能的角度来看这很糟糕)?

Cod*_*ray 6

我不知道什么getParentPath是。我假设它是一个搜索字符串中尾随反斜杠并使用它来剥离文件规范的函数。您不必自己定义这样的函数;Windows 已经为您提供了一个—— PathCchRemoveFileSpec。(请注意,这假定指定的路径实际上包含要删除的文件名。如果路径不包含文件名,它将删除尾随目录名。您还可以使用其他函数来验证路径是否包含文件规格。)

此函数的旧版本是PathRemoveFileSpec,这是您将在较新、更安全的函数不可用的下层操作系统上使用的函数。

在 Windows API 之外,还有其他方法可以做同样的事情。如果您的目标是 C++17,则可以使用filesystem::path该类。Boost 提供了类似的东西。或者,如果绝对必须,您可以使用类的find_last_of成员函数自己编写它std::string。(但最好不要重新发明轮子。当涉及到路径操作时,您可能不会想到很多边缘情况,并且您的测试可能不会揭示。)

您对这种方法的性能表示担忧。这是无稽之谈。从字符串中剥离一些字符并不是一个缓慢的操作。如果您从字符串的开头开始搜索,然后在找到文件规范后,制作字符串的第二个副本,再次从字符串的开头开始,那么它甚至不会变慢。这是一个简单的循环,搜索一个合理长度的字符串的字符,然后是一个简单的memcpy. 此操作绝对不可能成为执行文件 I/O 的代码的性能瓶颈。

但是,实现可能不会那么天真。您可以通过从路径字符串的末尾开始搜索来优化它,减少您必须遍历的字符数,并且如果允许操作原始字符串,您可以完全避免任何类型的内存复制。对于 C 风格的字符串,您只需用 NUL 字符 ( \0)替换尾随路径分隔符(分隔路径规范开头的分隔符)。对于 C++ 样式的字符串,您只需调用erase成员函数。

事实上,如果您真的关心性能,这几乎可以保证比通过系统调用从文件对象中检索包含文件夹要快。系统调用比一些编译器生成的、可内联的代码慢得多,以遍历字符串并去除子字符串。

一旦您获得了目录的路径,您就可以HANDLE通过调用CreateFile带有FILE_FLAG_BACKUP_SEMANTICS标志的函数来获取该目录的路径。(如果要检索目录的句柄,则必须传递该标志。


我已经测量过这很慢,正在寻找更快的方法。

你的测量是错误的。要么您犯了对调试构建进行基准测试的常见错误,其中标准库功能(例如,std::string)未优化,和/或真正的性能瓶颈是文件 I/O。CreateFile不是由任何发挥想象力早日功能。我几乎可以保证这将成为您的热点。


请注意,如果您还没有路径,则可以直接获取从 aHANDLE到文件的路径。正如评论中指出的那样,在 Windows Vista 和更高版本上,您只需要调用该GetFinalPathNameByHandle函数。MSDN 上的这篇文章提供了更多详细信息,包括示例代码和在 Windows 下层版本上使用的替代方法。

正如问题的评论中已经提到的,您可以通过MAX_PATH在堆栈上分配一个长度(甚至更大)的缓冲区来进一步优化它。这将编译为一条调整堆栈指针的指令,因此它也不会成为性能瓶颈。(好吧,我撒谎了:您实际上需要两条指令——一条在堆栈上创建空间,另一条用于释放堆栈上分配的空间。仍然不是性能问题。)那样的话,您甚至不必做任何动态内存分配。

请注意,为了获得最大的稳健性,尤其是在 Windows 10 上,您希望处理路径长于MAX_PATH. 在这种情况下,您的堆栈分配缓冲区将太小,您调用以填充它的函数将返回错误。处理该错误,并在空闲存储上分配更大的缓冲区。那会更慢,但这是一种边缘情况,可能不值得优化。99% 的常见情况将使用堆栈分配的缓冲区。

此外,eryksun 指出(在对此答案的评论中)虽然它很方便,但GetFinalPathNameByHandle需要多个系统调用来映射 NT 和 DOS 命名空间之间的文件对象并规范化路径。我没有拆解这个功能,所以我不能证实他的说法,但我没有理由怀疑他们。在正常情况下,您不会担心此类开销或可能的性能成本,但是由于这似乎是您的应用程序的一个大问题,您可以使用 eryksun 的调用GetFileInformationByHandleEx和请求FileNameInfo类的替代建议。GetFileInformationByHandleEx是一个通用的多用途函数,可以检索有关文件的所有不同类型的信息,包括路径。它的实现更简单,直接向下调用原生NtQueryInformationFile功能。我原以为GetFinalPathNameByHandle这只是一个提供此服务的用户模式包装器,但 eryksun 的研究表明,如果这确实是性能热点,它正在做额外的工作,您可能希望避免这些工作。我必须稍微限定一下GetFileInformationByHandleEx,为了检索FileNameInfo,必须创建一个 I/O 请求包 (IRP) 并调用底层设备驱动程序。这不是一个便宜的操作,所以我不确定标准化路径的额外开销是否真的很重要。但在这种情况下,使用该GetFileInformationByHandleEx方法并没有什么真正的危害,因为它是一个记录在案的函数。


如果您已经按照描述编写了代码,但仍然存在可衡量的性能问题,那么请发布该代码以供他人查看并帮助您优化。该代码审查堆栈交易所网站是一个伟大的地方得到这样的帮助,对工作代码。请随时在此答案下的评论中给我留下此类问题的链接,以免我错过。

无论您做什么,停止调用 ANSI 版本的 Windows API 函数(以A后缀结尾的函数)。您需要宽字符 (Unicode) 版本。它们以W后缀结尾,并处理由WCHAR(== wchar_t) 字符组成的字符串。除了 ANSI 版本由于不提供 Unicode 支持而被弃用了几十年这一事实(对于 2000 年之后编写的任何应用程序来说,支持路径中的 Unicode 字符都不是可选的),就像您关心性能一样,您应该知道这样一个事实,即所有带A后缀的 API 函数只是将传入的 ANSI 字符串转换为 Unicode 字符串然后委托给带W后缀的版本的存根。如果函数返回一个字符串,第二次转换也必须由A-suffixed 版本完成,因为所有本机 API 都使用 Unicode 字符串。性能并不是您应该避免调用 ANSI 函数的真正原因,但也许您会发现它更有说服力。

可能是一种方式做你想要(通过映射一个文件对象HANDLE包含它的目录),但它需要在NT原生API的使用无证。我在文档化的函数中根本看不到任何可以让您获取此信息的内容。它当然无法通过该GetFileInformationByHandleEx功能访问。无论好坏,用户模式文件系统 API 几乎完全基于路径。据推测,它是在内部跟踪的,但即使是记录在根目录HANDLE(例如,NtDeleteFile通过OBJECT_ATTRIBUTES结构)的NT 本机 API 函数也允许该字段为 NULL,在这种情况下使用完整路径字符串。

与往常一样,如果您提供了更多关于大局的详细信息,我们可能会提供更合适的解决方案。这就是评论者在提到 XY 问题时所驱动的。是的,人们质疑您的动机,因为这是我们提供最合适帮助的方式。