GetModuleFileName()
以缓冲区和缓冲区的大小作为input; 但是它的返回值只能告诉我们复制了多less个字符,如果大小不够( ERROR_INSUFFICIENT_BUFFER
)。
如何确定真正需要的缓冲区大小来保存GetModuleFileName()
整个文件名?
大多数人使用MAX_PATH
但我记得path可以超过(260默认定义)…
(使用零作为缓冲区大小的技巧不适用于此API – 我已经尝试过)
实现一些合理的策略,像MAX_PATH开始一样增大缓冲区大小,然后每个连续的大小1.5倍(或者更少的迭代次数)大于前一个大小。 迭代直到函数成功。
通常的方法是调用它将大小设置为零,并保证失败并提供分配足够缓冲区所需的大小。 分配一个缓冲区(不要忘记空间终止),并再次调用它。
在很多情况下, MAX_PATH
就足够了,因为许多文件系统限制了一个路径名的总长度。 但是,可以构建超过MAX_PATH
合法和有用的文件名,因此查询所需缓冲区可能是一个好建议。
不要忘记最终从提供它的分配器中返回缓冲区。
编辑:弗朗西斯在评论中指出,通常的食谱不适用于GetmoduleeFileName()
。 不幸的是,弗朗西斯在这一点上是绝对正确的,我唯一的借口是在提供一个“通常”的解决方案之前,我没有去查证。
我不知道这个API的作者是在想什么,除了有可能当它被引入时, MAX_PATH
确实是最大的可能路径,使得正确的配方变得容易。 只需在长度不小于MAX_PATH
字符的缓冲区中进行所有文件名操作。
哦,是的,不要忘记自1995年以来的路径名称,所以允许Unicode字符。 因为Unicode需要更多的空间,所以任何路径名都可以以\\?\
为前缀,以显式地请求为该名称删除其字节长度的MAX_PATH
限制。 这使问题复杂化。
MSDN在标题为“ 文件名,路径和命名空间 ”的文章中提到了路径长度:
最大路径长度
在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
的缓冲区,检索名称并检查错误。 如果适合,就完成了。 否则,如果以“ \\?\
”开头,得到大小为64KB的缓冲区(“最大路径32,767个字符是近似的”这个词在这里有点麻烦,所以我留下一些细节供进一步研究)然后再试一次。
溢出MAX_PATH
但不是以“ \\?\
”开头似乎是“不可能发生”的情况。 再次,那么该怎么做是一个你必须处理的细节。
对于以“ \\server\Share\
”开头的网络名称的路径长度限制,更不用说内核对象名称空间中以“ \\.\
”开头的名称。 上面的文章没有说,我不确定这个API是否可以返回这样的路径。
运用
extern char* _pgmptr
可能会工作。
从GetmoduleeFileName的文档:
全局变量_pgmptr会自动初始化为可执行文件的完整路径,并可用于检索可执行文件的完整路径名。
但是,如果我读了关于_pgmptr:
如果程序没有从命令行运行,_pgmptr可能被初始化为程序名(文件的基本名称,不带文件扩展名)或文件名,相对路径或完整路径。
任何人都知道_pgmptr是如何初始化的? 如果SO支持后续问题,我会把这个问题作为一个后续的问题。
虽然API是错误设计的证明,但解决方案其实非常简单。 简单但悲伤的是它必须是这样的,因为它可能需要多个内存分配。 以下是解决方案的一些要点:
你不能真正依靠不同的Windows版本之间的返回值,因为它可以在不同的Windows版本(例如XP)上具有不同的语义。
如果提供的缓冲区太小而不能保存字符串,则返回值是包含0终止符的字符数量。
如果提供的缓冲区足够大以容纳字符串,则返回值是不包含0结束符的字符数。
这意味着如果返回的值恰好等于缓冲区大小,您仍然不知道它是否成功。 可能有更多的数据。 或不。 最后,如果缓冲区的长度实际上大于所需的值,则只能确定成功。 可悲的是…
所以,解决的办法是从一个小缓冲区开始。 然后,我们调用GetmoduleeFileName传递确切的缓冲区长度(在TCHARs中)并将返回结果与它进行比较。 如果返回结果小于我们的缓冲区长度,则成功。 如果返回结果大于或等于我们的缓冲区长度,我们必须再次尝试更大的缓冲区。 冲洗并重复,直到完成。 完成后,我们对缓冲区进行字符串复制(strdup / wcsdup / tcsdup),清理并返回字符串副本。 该字符串将具有正确的分配大小,而不是来自临时缓冲区的可能开销。 请注意,调用者负责释放返回的字符串(strdup / wcsdup / tcsdup mallocs内存)。
请参阅下面的实现和使用代码示例。 我已经使用这个代码已经有十多年了,包括企业文档管理软件在内的很多很长的路径。 代码可以以各种方式进行优化,例如首先将返回的字符串加载到本地缓冲区(TCHAR buf [256])中。 如果该缓冲区太小,则可以启动动态分配循环。 其他优化是可能的,但这超出了范围。
实施和使用示例:
/* Ensure Win32 API Unicode setting is in sync with CRT Unicode setting */ #if defined(_UNICODE) && !defined(UNICODE) # define UNICODE #elif defined(UNICODE) && !defined(_UNICODE) # define _UNICODE #endif #include <stdio.h> /* not needed for our function, just for printf */ #include <tchar.h> #include <windows.h> LPCTSTR GetMainmoduleePath(void) { TCHAR* buf = NULL; DWORD bufLen = 256; DWORD retLen; while (32768 >= bufLen) { if (!(buf = (TCHAR*)malloc(sizeof(TCHAR) * (size_t)bufLen)) { /* Insufficient memory */ return NULL; } if (!(retLen = GetmoduleeFileName(NULL, buf, bufLen))) { /* GetmoduleeFileName failed */ free(buf); return NULL; } else if (bufLen > retLen) { /* Success */ LPCTSTR result = _tcsdup(buf); /* Caller should free returned pointer */ free(buf); return result; } free(buf); bufLen <<= 1; } /* Path too long */ return NULL; } int main(int argc, char* argv[]) { LPCTSTR path; if (!(path = GetMainmoduleePath())) { /* Insufficient memory or path too long */ return 0; } _tprintf("%s\n", path); free(path); /* GetMainmoduleePath malloced memory using _tcsdup */ return 0; }
说了这么多,我想指出你需要非常了解GetmoduleeFileName(Ex)的其他各种注意事项。 32/64-bit / WOW64之间有不同的问题。 此外,输出不一定是一个完整的,长的路径,但很可能是一个短文件名或受到路径别名。 我希望当你使用这样一个函数,目的是为调用者提供一个可用的,可靠的完整的,长的路径,因此,我建议确实确保返回一个可用的,可靠的,完整的,长的绝对路径,它可以在各种Windows版本和体系结构(也是32/64-bit / WOW64)之间移植。 如何有效地做到这一点超出了这里的范围。
虽然这是目前最糟糕的Win32 API之一,但是我希望你有很多编程的乐趣。
Windows无法正确处理超过260个字符的路径,所以只需使用MAX_PATH即可。 您不能运行路径长度超过MAX_PATH的程序。
我的例子是“如果起初你不成功,缓冲区长度加倍”的具体实现。 它检索正在运行的可执行文件的路径,使用一个字符串(实际上是一个wstring
,因为我希望能够处理Unicode)作为缓冲区。 要确定它何时成功检索完整路径,它将检查从GetmoduleeFileNameW
返回的值与wstring::length()
返回的值,然后使用该值来调整最终字符串的大小,以便去除多余的空字符。 如果失败,则返回一个空字符串。
inline std::wstring getPathToExecutableW() { static const size_t INITIAL_BUFFER_SIZE = MAX_PATH; static const size_t MAX_ITERATIONS = 7; std::wstring ret; DWORD bufferSize = INITIAL_BUFFER_SIZE; for (size_t iterations = 0; iterations < MAX_ITERATIONS; ++iterations) { ret.resize(bufferSize); DWORD charsReturned = GetmoduleeFileNameW(NULL, &ret[0], bufferSize); if (charsReturned < ret.length()) { ret.resize(charsReturned); return ret; } else { bufferSize *= 2; } } return L""; }
我的做法是使用argv,假设您只想获取正在运行的程序的文件名。 当您尝试从不同的模块获取文件名时,唯一可行的方法就是不使用任何其他技巧,现在可以在这里找到一个实现。
// assume argv is there and a char** array int nAllocCharCount = 1024; int nBufSize = argv[0][0] ? strlen((char *) argv[0]) : nAllocCharCount; TCHAR * pszCompleteFilePath = new TCHAR[nBufSize+1]; nBufSize = GetmoduleeFileName(NULL, (TCHAR*)pszCompleteFilePath, nBufSize); if (!argv[0][0]) { // resize memory until enough is available while (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { delete[] pszCompleteFilePath; nBufSize += nAllocCharCount; pszCompleteFilePath = new TCHAR[nBufSize+1]; nBufSize = GetmoduleeFileName(NULL, (TCHAR*)pszCompleteFilePath, nBufSize); } TCHAR * pTmp = pszCompleteFilePath; pszCompleteFilePath = new TCHAR[nBufSize+1]; memcpy_s((void*)pszCompleteFilePath, nBufSize*sizeof(TCHAR), pTmp, nBufSize*sizeof(TCHAR)); delete[] pTmp; pTmp = NULL; } pszCompleteFilePath[nBufSize] = '\0'; // do work here // variable 'pszCompleteFilePath' contains always the complete path now // cleanup delete[] pszCompleteFilePath; pszCompleteFilePath = NULL;
我没有argv没有包含文件路径(Win32和Win32控制台应用程序)的情况。 但是,以防万一有一个解决方案已经在上面描述。 看起来有点丑陋,但仍然完成了工作。