GetModuleFileName()
将缓冲区和缓冲区大小作为输入;然而,它的返回值只能告诉我们复制了多少个字符,以及大小是否不够(ERROR_INSUFFICIENT_BUFFER
)。
如何确定真实的需要的缓冲区大小来保存GetModuleFileName()
的整个文件名?
大多数人使用MAX_PATH
,但我记得路径可以超过这个值(默认定义为260)。
(The使用零作为缓冲区大小的技巧不适用于此API -我以前已经尝试过)
GetModuleFileName()
将缓冲区和缓冲区大小作为输入;然而,它的返回值只能告诉我们复制了多少个字符,以及大小是否不够(ERROR_INSUFFICIENT_BUFFER
)。
如何确定真实的需要的缓冲区大小来保存GetModuleFileName()
的整个文件名?
大多数人使用MAX_PATH
,但我记得路径可以超过这个值(默认定义为260)。
(The使用零作为缓冲区大小的技巧不适用于此API -我以前已经尝试过)
9条答案
按热度按时间pw136qt21#
通常的方法是调用它,将大小设置为零,保证失败,并提供分配足够缓冲区所需的大小。分配一个缓冲区(不要忘记nul-termination的空间)并再次调用它。
在许多情况下,
MAX_PATH
就足够了,因为许多文件系统限制了路径名的总长度。但是,也有可能构造超过MAX_PATH
的法律的且有用的文件名,因此查询所需的缓冲区可能是一个不错的建议。不要忘记最终从提供缓冲区的分配器返回缓冲区。
编辑:弗朗西斯在评论中指出,通常的配方不适用于
GetModuleFileName()
。不幸的是,弗朗西斯在这一点上是绝对正确的,我唯一的借口是,我没有去看看它,以核实之前,提供了一个“通常”的解决方案。我不知道那个API的作者是怎么想的,除了当它被引入时,
MAX_PATH
可能是最大的可能路径,使得正确的配方很容易。只需在长度不小于MAX_PATH
个字符的缓冲区中执行所有文件名操作。哦,对了,别忘了1995年以后的路径名允许使用Unicode字符。因为Unicode占用更多空间,所以任何路径名前面都可以加上
\\?\
,以显式请求删除该名称的字节长度限制MAX_PATH
。这使问题复杂化了。MSDN在标题为**File Names, Paths, and Namespaces**的文章中对路径长度有以下说法:
最大路径长度
在Windows API中(以下段落中讨论了一些例外情况),路径的最大长度为
MAX_PATH
,定义为260个字符。本地路径按以下顺序构造:驱动器盘符、冒号、反斜杠、由反斜杠分隔的组件和终止空字符。例如,驱动器D上的最大路径是“D:\<some 256 character path string><NUL>
”,其中“<NUL>
”表示当前系统代码页的不可见终止空字符。(此处使用字符<
>
是为了视觉清晰,不能作为有效路径字符串的一部分。)注意Windows API中的文件I/O函数将“
/
”转换为“\
”,作为将名称转换为NT样式名称的一部分,除非使用“\\?\
”前缀,如以下各节所述。Windows API有许多函数,这些函数也具有Unicode版本,以允许最大总路径长度为32,767个字符的扩展长度路径。这种类型的路径由反斜杠分隔的组件组成,每个组件的最大值为
GetVolumeInformation
函数的lpMaximumComponentLength
参数中返回的值。要指定扩展长度路径,请使用“\\?\
”前缀。例如,“\\?\D:\<very long path>
”。(此处使用字符<
>
是为了视觉清晰,不能作为有效路径字符串的一部分。)注意:32,767个字符的最大路径是近似值,因为“
\\?\
”前缀可能会在运行时被系统扩展为更长的字符串,并且此扩展适用于总长度。“
\\?\
”前缀也可以用于根据通用命名约定(UNC)构造的路径。要使用UNC指定这样的路径,请使用“\\?\UNC\
”前缀。例如,“\\?\UNC\server\share
”,其中“server”是计算机的名称,“share”是共享文件夹的名称。这些前缀不用作路径本身的一部分。它们表明路径应该以最小的修改传递给系统,这意味着不能使用正斜杠表示路径分隔符,也不能使用句点表示当前目录。此外,您不能将“\\?\
”前缀与相对路径一起使用,因此相对路径仅限于MAX_PATH
字符,如前面针对不使用“\\?\
”前缀的路径所述。使用API创建目录时,指定的路径不能太长,不能追加8.3文件名(即目录名不能超过
MAX_PATH
减12)。shell和文件系统有不同的要求。可以使用Windows API创建shell用户界面可能无法处理的路径。
因此,一个简单的答案是分配一个大小为
MAX_PATH
的缓冲区,检索名称并检查错误。如果合适,你就完成了。否则,如果它以“\\?\
”开头,则获取一个大小为64 KB左右的缓冲区(上面的短语“32,767个字符的最大路径是近似的”在这里有点麻烦,所以我留下一些细节供进一步研究)并重试。溢出
MAX_PATH
但不以“\\?\
”开头似乎是“不可能发生”的情况。再一次,接下来要做什么是你必须处理的细节。对于以“
\\Server\Share\
”开头的网络名称的路径长度限制,也可能存在一些混淆,更不用说以“\\.\
”开头的内核对象名称空间中的名称了。上面的文章没有说,我不确定这个API是否可以返回这样的路径。cgvd09ve2#
实施一些合理的策略来增加缓冲区,比如从MAX_PATH开始,然后使每个连续的大小比前一个大1,5倍(或2倍,迭代次数较少)。迭代直到函数成功。
o8x7eapl3#
使用
可能会有用
从GetModuleFileName的文档中:
全局变量_pgmptr会自动初始化为可执行文件的完整路径,并可用于检索可执行文件的完整路径名。
但是如果我读到关于_pgmptr:
当程序不是从命令行运行时,_pgmptr可能初始化为程序名(文件的基本名,不带文件扩展名)或文件名、相对路径或完整路径。
谁知道_pgmptr是如何初始化的?如果SO支持后续问题,我会把这个问题作为后续问题。
2wnc66cl4#
虽然API证明了糟糕的设计,但解决方案实际上非常简单。很简单,但不幸的是,它必须这样做,因为它有点像性能Pig,因为它可能需要多个内存分配。以下是解决方案的一些关键点:
这意味着如果返回值正好等于缓冲区大小,您仍然不知道它是否成功。可能会有更多的数据。最后,只有当缓冲区长度实际上大于所需长度时,才能确定成功。很遗憾...
因此,解决方案是从一个小缓冲区开始。然后调用GetModuleFileName,传递精确的缓冲区长度(以TCHAR为单位),并将返回结果与之进行比较。如果返回的结果小于我们的缓冲区长度,则它成功了。如果返回结果大于或等于缓冲区长度,我们必须使用更大的缓冲区再试一次。冲洗并重复直到完成。完成后,我们创建缓冲区的字符串副本(strdup/wcsdup/tcsdup),清理并返回字符串副本。这个字符串将具有正确的分配大小,而不是临时缓冲区的可能开销。请注意,调用者负责释放返回的字符串(strdup/wcsdup/tcsdup错误内存)。
参见下面的实现和用法代码示例。我已经使用这段代码十多年了,包括在企业文档管理软件中,那里可能有很多相当长的路径。当然,代码可以通过各种方式进行优化,例如首先将返回的字符串加载到本地缓冲区(TCHAR buf[256])。如果该缓冲区太小,则可以启动动态分配循环。其他优化也是可能的,但这超出了这里的范围。
实现及使用示例:
说了这么多,我想指出的是,你需要非常清楚GetModuleFileName(Ex)的其他各种警告。32/64位/WOW 64之间存在各种问题。同样,输出不一定是完整的长路径,但很可能是短文件名或路径别名。我希望当你使用这样一个函数时,目标是为调用者提供一个可用的、可靠的完整的、长的路径,因此我建议确保返回一个可用的、可靠的、完整的、长的绝对路径,以这样的方式,它是在各种Windows版本和架构之间移植的(再次是32/64位/WOW 64)。如何有效地做到这一点超出了这里的范围。
虽然这是现存最糟糕的Win32 API之一,但我还是希望你能有很多编码的乐趣。
2izufjch5#
我的例子是“如果一开始没有成功,就将缓冲区的长度加倍”方法的具体实现。它检索正在运行的可执行文件的路径,使用字符串(实际上是
wstring
,因为我希望能够处理Unicode)作为缓冲区。为了确定何时成功检索到完整路径,它将GetModuleFileNameW
返回的值与wstring::length()
返回的值进行比较,然后使用该值调整最终字符串的大小,以去除多余的空字符。如果失败,则返回空字符串。7vux5j2d6#
下面是另一个使用std::wstring的解决方案:
fwzugrvs7#
这里有一个在Free Pascal(FPC)/Delphi中的实现,以防任何人需要它:
8xiog9wr8#
Windows无法正确处理长度超过260个字符的路径,因此只需使用MAX_PATH即可。不能运行路径长于MAX_PATH的程序。
332nm8kg9#
我的方法是使用argv,假设您只想获取正在运行的程序的文件名。当你试图从一个不同的模块获取文件名时,唯一安全的方法已经描述过了,可以在这里找到一个实现。
我还没有遇到过argv不包含文件路径的情况(Win32和Win32-console应用程序)。但以防万一,有一个回退到上面已经描述的解决方案。对我来说似乎有点丑陋,但仍然完成了工作。