如何从文件“ HANDLE”中获取一个“ HANDLE”到包含目录?

时间:2019-06-11 06:11:26

标签: c++ c windows winapi file-io

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

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

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;
}

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

1 个答案:

答案 0 :(得分:3)

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

此功能的较早版本为PathRemoveFileSpec,这是在较新的安全功能不可用的下层操作系统上使用的版本。

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

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

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

实际上,如果您真的在乎性能,那么实际上可以保证它比进行系统调用从文件对象中检索包含的文件夹要快。 系统调用比编译器生成的,可插入的代码迭代字符串并去除子字符串要慢得多。

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


  

我测量出这很慢,并且正在寻找一种更快的方法。

您的测量结果有误。您可能犯了基准测试调试版本的常见错误,其中标准库功能(例如std::string)没有得到优化,和/或真正的性能瓶颈是文件I / O。 CreateFile在任何想像力的范围内都不是快速功能。我几乎可以保证这将成为您的热点。


请注意,如果您还没有路径,则很容易获得从HANDLE到文件的路径。如注释中所述,在Windows上在Vista及更高版本中,您只需要调用GetFinalPathNameByHandle函数。 MSDN上的this article中提供了更多详细信息,包括示例代码和在Windows较低版本上使用的替代方法。

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

请注意,为了获得最大的鲁棒性,尤其是在Windows 10上,您要处理路径比MAX_PATH长的情况。在这种情况下,您分配给堆栈的缓冲区会太小,而您调用以填充它的函数将返回错误。处理该错误,并在免费存储区上分配更大的缓冲区。这样会比较慢,但这只是一个边缘情况,可能不是值得优化的情况。在99%的常见情况下,将使用堆栈分配的缓冲区。

此外,eryksun指出(在此答案的注释中),尽管方便,GetFinalPathNameByHandle需要多个系统调用才能在NT和DOS名称空间之间映射文件对象并标准化路径。我尚未反汇编此功能,因此无法确认他的主张,但我没有理由怀疑它们。在正常情况下,您无需担心此类开销或可能的性能成本,但是由于这似乎是您的应用程序的主要问题,因此您可以使用eryksun的替代建议,即调用GetFileInformationByHandleEx并请求{{ 1}}类。 FileNameInfo是一种通用的通用功能,可以检索有关文件的所有不同种类的信息,包括路径。它的实现更简单,直接调用原生NtQueryInformationFile函数。我本以为GetFileInformationByHandleEx只是提供此服务的用户模式包装程序,但是eryksun的研究表明,它确实在做额外的工作,如果这确实是性能热点,您可能要避免。我必须稍微指出一点,以指出GetFinalPathNameByHandle(为了检索GetFileInformationByHandleEx)将必须创建I / O请求包(IRP)并调用底层设备驱动程序。这不是一个便宜的操作,所以我不确定标准化路径的额外开销是否真的很重要。但是在这种情况下,使用FileNameInfo方法并没有真正的危害,因为它是一个文档功能。


如果您如上所述编写了代码,但是仍然遇到可衡量的性能问题,那么请发布该代码,以供其他人进行检查并帮助您进行优化。 Code Review Stack Exchange网站是一个获得类似工作代码帮助的好地方。随时在此答案下的评论中给我留下这样一个问题的链接,这样我就不会错过它。

无论做什么,都不要调用Windows API函数(以GetFileInformationByHandleEx后缀结尾的ANSI版本)。宽字符(Unicode)版本。这些以A后缀结尾,并使用由W(== {WCHAR)个字符组成的字符串。除了ANSI版本已经过了几十年的事实,因为它们不提供Unicode支持(对于2000年之后编写的任何应用程序都不支持路径中的Unicode字符,这不是可选的),就像您关心性能一样,您应该知道以下事实:所有带有wchar_t后缀的API函数都是存根,它们将传入的ANSI字符串转换为Unicode字符串,然后委派给带有A后缀的版本。如果函数返回字符串,则必须使用后缀W的版本进行第二次转换,因为所有本机API均可以使用Unicode字符串。性能并不是避免调用ANSI函数的真正原因,但也许这是使您更有说服力的原因。

可能有 一种方法来完成您想要的操作(通过A将文件对象映射到其包含的目录),但是这需要使用未记录的NT本机API。我在记录的函数中根本看不到任何可以让您获得此信息的东西。当然,无法通过HANDLE函数进行访问。不管是好是坏,用户模式文件系统API几乎完全基于路径。大概是在内部对其进行 跟踪,但是即使采用根目录GetFileInformationByHandleEx(例如,HANDLE通过NtDeleteFile结构)的已记录的NT本机API函数也允许此字段为NULL,在这种情况下,将使用完整路径字符串。

和往常一样,如果您提供了更大的详细信息,我们可能会提供更合适的解决方案。这是评论者提到XY问题时要进行的工作。是的,人们在质疑您的动机,因为这是我们提供最适当帮助的方式。