How can I calculate the complete buffer size for GetModuleFileName?
如何确定容纳
大多数人使用
(使用零作为缓冲区大小的技巧不适用于此API-我之前已经尝试过)
通常的做法是调用它,将大小设置为零,以确保失败并提供分配足够缓冲区所需的大小。分配一个缓冲区(不要忘记保留nul的空间)并再次调用它。
在许多情况下,
不要忘记最终从提供缓冲的分配器返回缓冲。
编辑:弗朗西斯在评论中指出,通常的食谱不适用于
我不知道该API的作者在想什么,只是在引入它时,
哦,是的,不要忘记自1995年以来的路径名就允许使用Unicode字符。因为Unicode占用了更多空间,所以任何路径名都可以以
MSDN在标题为"文件名,路径和命名空间"的文章中对路径长度进行了说明:
Maximum Path Length
In the Windows API (with some
exceptions discussed in the following
paragraphs), the maximum length for a
path isMAX_PATH , which is defined as
260 characters. A local path is
structured in the following order:
drive letter, colon, backslash,
components separated by backslashes,
and a terminating null character. For
example, the maximum path on drive D
is"D:\ " where"" represents
the invisible terminating null
character for the current system
codepage. (The characters< > are used
here for visual clarity and cannot be
part of a valid path string.)Note File I/O functions in the
Windows API convert"/ " to"\ " as part
of converting the name to an NT-style
name, except when using the"\\?\ "
prefix as detailed in the following
sections.The Windows API has many functions
that also have Unicode versions to
permit an extended-length path for a
maximum total path length of 32,767
characters. This type of path is
composed of components separated by
backslashes, each up to the value
returned in the
lpMaximumComponentLength parameter of
theGetVolumeInformation function. To
specify an extended-length path, use
the"\\?\ " prefix. For example,
"\\?\D:\ ". (The
characters< > are used here for
visual clarity and cannot be part of a
valid path string.)Note The maximum path of 32,767
characters is approximate, because the
"\\?\ " prefix may be expanded to a
longer string by the system at run
time, and this expansion applies to
the total length.The"
\\?\ " prefix can also be used
with paths constructed according to
the universal naming convention (UNC).
To specify such a path using UNC, use
the"\\?\UNC\ " prefix. For example,
"\\?\UNC\server\share ", where"server"
is the name of the machine and"share"
is the name of the shared folder.
These prefixes are not used as part of
the path itself. They indicate that
the path should be passed to the
system with minimal modification,
which means that you cannot use
forward slashes to represent path
separators, or a period to represent
the current directory. Also, you
cannot use the"\\?\ " prefix with a
relative path, therefore relative
paths are limited toMAX_PATH
characters as previously stated for
paths not using the"\\?\ " prefix.When using an API to create a
directory, the specified path cannot
be so long that you cannot append an
8.3 file name (that is, the directory name cannot exceedMAX_PATH minus 12).The shell and the file system have
different requirements. It is possible
to create a path with the Windows API
that the shell user interface might
not be able to handle.
因此,一个简单的答案就是分配大小为
对于以"
实施一些合理的策略来增加缓冲区,例如以MAX_PATH开头,然后使每个连续大小都比上一个大1.5倍(或减少迭代2倍)。迭代直到函数成功。
虽然API证明设计不好,但解决方案实际上非常简单。简单但令人遗憾的是,必须这样做,因为这可能会降低性能,因为它可能需要多个内存分配。这是解决方案的一些关键点:
-
您不能真正依赖于不同Windows版本之间的返回值,因为它在不同Windows版本(例如XP)上可能具有不同的语义。
-
如果提供的缓冲区太小而无法容纳字符串,则返回值为包含0终止符的字符数。
-
如果提供的缓冲区足够大以容纳字符串,则返回值是不包括0终止符的字符数。
这意味着,如果返回的值恰好等于缓冲区大小,则您仍然不知道它是否成功。可能会有更多数据。或不。最后,只有缓冲区长度实际上大于要求的长度,您才能确定成功。可悲的是...
因此,解决方案是从一个小的缓冲区开始。然后,我们调用GetModuleFileName传递确切的缓冲区长度(以TCHAR为单位),并将返回结果与其进行比较。如果返回结果小于我们的缓冲区长度,则成功。如果返回结果大于或等于我们的缓冲区长度,则必须使用更大的缓冲区再试一次。冲洗并重复直到完成。完成后,我们创建缓冲区的字符串副本(strdup / wcsdup / tcsdup),清理并返回字符串副本。该字符串将具有正确的分配大小,而不是临时缓冲区的可能开销。请注意,调用者负责释放返回的字符串(strdup / wcsdup / tcsdup mallocs内存)。
有关实现和使用代码示例,请参见下文。我使用该代码已有十多年了,包括在企业文档管理软件中,该软件可能会有很多很长的路径。当然,可以通过各种方式来优化代码,例如,首先将返回的字符串加载到本地缓冲区(TCHAR buf [256])中。如果该缓冲区太小,则可以启动动态分配循环。其他优化也是可能的,但这超出了本文的范围。
实现和用法示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | /* 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 GetMainModulePath(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 = GetModuleFileName(NULL, buf, bufLen))) { /* GetModuleFileName 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 = GetMainModulePath())) { /* Insufficient memory or path too long */ return 0; } _tprintf("%s ", path); free(path); /* GetMainModulePath malloced memory using _tcsdup */ return 0; } |
说了这么多,我想指出您需要非常了解GetModuleFileName(Ex)的其他警告。 32/64位/ WOW64之间存在各种问题。同样,输出不一定是完整的长路径,而是很可能是短文件名或受到路径别名的影响。我希望当您使用这样的功能时,目标是为调用者提供可用的,可靠的完整,长路径,因此,我建议确实确保以这样的方式返回可用的,可靠的,完整,长绝对路径。它可以在各种Windows版本和体系结构(同样是32??/64位/ WOW64)之间移植。如何有效地做到这一点超出了本文的范围。
尽管这是现有的最差的Win32 API之一,但我仍然希望您有很多编码乐趣。
运用
1 | extern char* _pgmptr |
可能有用。
从GetModuleFileName的文档中:
The global variable _pgmptr is automatically initialized to the full path of the executable file, and can be used to retrieve the full path name of an executable file.
但是,如果我读到有关_pgmptr的内容:
When a program is not run from the command line, _pgmptr might be initialized to the program name (the file's base name without the file name extension) or to a file name, relative path, or full path.
有谁知道_pgmptr的初始化方式吗?如果SO支持后续问题,我会将此问题发布为后续问题。
这是std :: wstring的另一个解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | DWORD getCurrentProcessBinaryFile(std::wstring& outPath) { // @see https://msdn.microsoft.com/en-us/magazine/mt238407.aspx DWORD dwError = 0; DWORD dwResult = 0; DWORD dwSize = MAX_PATH; SetLastError(0); while (dwSize <= 32768) { outPath.resize(dwSize); dwResult = GetModuleFileName(0, &outPath[0], dwSize); dwError = GetLastError(); /* if function has failed there is nothing we can do */ if (0 == dwResult) { return dwError; } /* check if buffer was too small and string was truncated */ if (ERROR_INSUFFICIENT_BUFFER == dwError) { dwSize *= 2; dwError = 0; continue; } /* finally we received the result string */ outPath.resize(dwResult); return 0; }; return ERROR_BUFFER_OVERFLOW; } |
我的示例是"如果一开始不成功,则将缓冲区的长度加倍"方法的具体实现。它使用字符串(实际上是一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 = GetModuleFileNameW(NULL, &ret[0], bufferSize); if (charsReturned < ret.length()) { ret.resize(charsReturned); return ret; } else { bufferSize *= 2; } } return L""; } |
Windows无法正确处理超过260个字符的路径,因此只能使用MAX_PATH。
您不能运行路径长于MAX_PATH的程序。
我的方法是使用argv,假设您只想获取正在运行的程序的文件名。当您尝试从其他模块获取文件名时,已经描述了没有任何其他技巧的唯一安全方法,可以在此处找到实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // 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 = GetModuleFileName(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 = GetModuleFileName(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-console应用程序)的情况。但是以防万一上述解决方案存在后备情况。对我来说似乎有点丑陋,但仍然可以完成工作。