我不知道getParentPath 是什么。我假设它是一个在字符串中搜索尾随反斜杠并使用它来剥离文件规范的函数。您不必自己定义这样的函数; Windows 已经为您提供了一个—PathCchRemoveFileSpec。 (请注意,这假定指定的路径实际上包含要删除的文件名。如果路径不包含文件名,它将删除尾随目录名。您可以使用其他函数来验证路径是否包含文件规范。)
这个函数的旧版本是PathRemoveFileSpec,你可以在没有更新、更安全的函数的低级操作系统上使用它。
在 Windows API 之外,还有其他方法可以做同样的事情。如果您的目标是 C++17,则有 filesystem::path 类。 Boost 提供了类似的东西。或者,如果您绝对需要,您可以使用std::string 类的find_last_of 成员函数自己编写它。 (但最好不要重新发明轮子。在涉及到路径操作时,有很多边缘情况是您可能不会想到的,而且您的测试也可能不会揭示。)
您对这种方法的性能表示担忧。这是无稽之谈。 从字符串中删除一些字符并不是一个缓慢的操作。如果您从字符串的开头开始搜索,然后在找到文件规范后复制第二个副本,这甚至不会很慢字符串,再次从字符串的开头开始。这是一个简单的循环搜索合理长度字符串的字符,然后是一个简单的memcpy。 此操作绝对不可能成为执行文件 I/O 的代码的性能瓶颈。
但是,实现可能甚至不会那么幼稚。您可以通过从路径字符串的 end 开始搜索来优化它,减少您必须遍历的字符数,并且如果允许,您可以完全避免任何类型的内存复制操纵原始字符串。使用 C 风格的字符串,您只需用 NUL 字符 (\0) 替换尾随路径分隔符(分隔路径规范开头的分隔符)。使用 C++ 风格的字符串,您只需调用 erase 成员函数。
事实上,如果您真的关心性能,这实际上保证比进行系统调用从文件对象中检索包含文件夹要快。 系统调用比一些编译器生成的、可内联的代码来遍历字符串并去除子字符串要慢得多。
一旦你有了目录的路径,你就可以通过调用带有FILE_FLAG_BACKUP_SEMANTICS标志的CreateFile函数来获得一个HANDLE。(必须传递那个标志如果你想检索一个目录的句柄。
我测得这很慢,正在寻找更快的方法。
您的测量结果有误。要么你犯了对调试版本进行基准测试的常见错误,其中标准库功能(例如,std::string)没有优化,和/或真正的性能瓶颈是文件 I/O。 CreateFile 不是任何想象中的快速功能。我几乎可以保证这将成为您的热点。
请注意,如果您还没有路径,则可以直接获取从 HANDLE 到文件的路径。 正如 cmets 中所指出的,在 Windows Vista 和稍后,您只需调用GetFinalPathNameByHandle 函数。在 MSDN 上的this article 中提供了更多详细信息,包括示例代码和用于 Windows 低级版本的替代方法。
正如问题的 cmets 中已经提到的,您可以通过在堆栈上分配长度为 MAX_PATH(或者甚至更大)的缓冲区来进一步优化这一点。这编译为一条指令来调整堆栈指针,因此它也不会成为性能瓶颈。 (好吧,我撒谎了:你实际上需要 两条 指令——一条用于在堆栈上创建空间,另一条用于释放堆栈上分配的空间。这仍然不是性能问题。)这样,您甚至不必进行任何动态内存分配。
请注意,为了获得最大的稳健性,尤其是在 Windows 10 上,您需要处理路径长于 MAX_PATH 的情况。在这种情况下,您的堆栈分配缓冲区将太小,您调用填充它的函数将返回错误。处理该错误,并在空闲存储区分配更大的缓冲区。这会慢一些,但这是一个边缘情况,可能不值得优化。 99% 的常见情况将使用堆栈分配的缓冲区。
此外,eryksun 指出(在此答案的 cmets 中),尽管很方便,GetFinalPathNameByHandle 需要多个系统调用来映射 NT 和 DOS 命名空间之间的文件对象并规范化路径。我没有拆解这个功能,所以我无法证实他的说法,但我没有理由怀疑他们。在正常情况下,您不会担心这种开销或可能的性能成本,但由于这似乎是您的应用程序的一个大问题,您可以使用 eryksun 的替代建议,即调用 GetFileInformationByHandleEx 并请求 FileNameInfo 类. GetFileInformationByHandleEx 是一个通用的多用途函数,可以检索有关文件的所有不同类型的信息,包括路径。它的实现更简单,直接调用原生的NtQueryInformationFile函数。我原以为GetFinalPathNameByHandle 只是提供此服务的用户模式包装器,但 eryksun 的研究表明,如果这确实是一个性能热点,您可能希望避免它做额外的工作。我必须通过注意GetFileInformationByHandleEx 来稍微限定这一点,以便检索FileNameInfo,将不得不创建一个 I/O 请求数据包 (IRP) 并调用底层设备驱动程序。这不是一个便宜的操作,所以我不确定规范化路径的额外开销是否真的很重要。但在这种情况下,使用 GetFileInformationByHandleEx 方法并没有真正的危害,因为它是一个文档化的函数。
如果您已按照所述编写代码,但仍存在可衡量的性能问题,请发布该代码以供他人审核并帮助您优化。 Code Review Stack Exchange 站点是在工作代码方面获得类似帮助的好地方。请随时在此答案下的评论中给我留下指向此类问题的链接,以免我错过。
无论您做什么,请停止调用 ANSI 版本的 Windows API 函数(以 A 后缀结尾的函数)。字符 (Unicode) 版本。这些以W 后缀结尾,并使用由WCHAR (== wchar_t) 字符组成的字符串。除了 ANSI 版本由于不提供 Unicode 支持(2000 年后编写的任何应用程序在路径中支持 Unicode 字符不是可选的)而已被弃用几十年这一事实之外,就像您关心性能一样,您应该知道所有A-suffixed API 函数只是将传入的 ANSI 字符串转换为 Unicode 字符串然后委托给W-suffixed 版本的存根。如果函数返回一个字符串,则第二次转换也必须由A-suffixed 版本完成,因为所有本机 API 都使用 Unicode 字符串。性能并不是您应该避免调用 ANSI 函数的真正原因,但也许它会让您觉得更有说服力。
可能有一种方法来做你想做的事(通过HANDLE 将文件对象映射到它的包含目录),但它需要未记录的 NT 原生 API 使用。我在记录的函数中根本看不到任何可以让您获取此信息的内容。它当然不能通过GetFileInformationByHandleEx 函数访问。无论好坏,用户模式文件系统 API 几乎完全基于路径。据推测,它是在内部跟踪的,但即使是记录在案的 NT 本机 API 函数,它们采用根目录 HANDLE(例如,NtDeleteFile 通过 OBJECT_ATTRIBUTES 结构)允许此字段为 NULL,在这种情况下使用完整路径字符串。
与往常一样,如果您提供了有关大局的更多详细信息,我们可能会提供更合适的解决方案。这就是评论者在提到 XY 问题时所追求的。是的,人们质疑您的动机,因为这是我们提供最适当帮助的方式。