windows 如何计算 GetModuleFileName 的完整缓冲区大小?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/805814/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 12:22:50  来源:igfitidea点击:

How can I calculate the complete buffer size for GetModuleFileName?

windowswinapigetmodulefilename

提问by Francis

The GetModuleFileName()takes a buffer and size of buffer as input; however its return value can only tell us how many characters is has copied, and if the size is not enough (ERROR_INSUFFICIENT_BUFFER).

GetModuleFileName()需要一个缓冲器和缓冲器作为输入的大小; 但是它的返回值只能告诉我们复制了多少个字符,以及大小是否不够(ERROR_INSUFFICIENT_BUFFER)。

How do I determine the real required buffer size to hold entire file name for GetModuleFileName()?

我如何确定真正需要的缓冲区大小来保存整个文件名GetModuleFileName()

Most people use MAX_PATHbut I remember the path can exceed that (260 by default definition)...

大多数人使用,MAX_PATH但我记得路径可以超过(默认定义为 260)...

(The trick of using zero as size of buffer does not work for this API - I've already tried before)

(使用零作为缓冲区大小的技巧不适用于此 API - 我之前已经尝试过)

采纳答案by sharptooth

Implement some reasonable strategy for growing the buffer like start with MAX_PATH, then make each successive size 1,5 times (or 2 times for less iterations) bigger then the previous one. Iterate until the function succeeds.

实施一些合理的策略来增加缓冲区,例如从 MAX_PATH 开始,然后使每个连续大小比前一个大 1.5 倍(或 2 倍以减少迭代)。迭代直到函数成功。

回答by RBerteig

The usual recipe is to call it setting the size to zero and it is guaranteed to fail and provide the size needed to allocate sufficient buffer. Allocate a buffer (don't forget room for nul-termination) and call it a second time.

通常的方法是调用它,将大小设置为零,它保证失败并提供分配足够缓冲区所需的大小。分配一个缓冲区(不要忘记空终止的空间)并再次调用它。

In a lot of cases MAX_PATHis sufficient because many of the file systems restrict the total length of a path name. However, it is possible to construct legal and useful file names that exceed MAX_PATH, so it is probably good advice to query for the required buffer.

在很多情况下MAX_PATH就足够了,因为许多文件系统限制了路径名的总长度。但是,可以构造超过 的合法且有用的文件名MAX_PATH,因此查询所需的缓冲区可能是一个很好的建议。

Don't forget to eventually return the buffer from the allocator that provided it.

不要忘记最终从提供它的分配器返回缓冲区。

Edit:Francis points out in a comment that the usual recipe doesn't work for GetModuleFileName(). Unfortunately, Francis is absolutely right on that point, and my only excuse is that I didn't go look it up to verify before providing a "usual" solution.

编辑:弗朗西斯在评论中指出,通常的配方不适用于GetModuleFileName(). 不幸的是,弗朗西斯在这一点上是绝对正确的,我唯一的借口是在提供“通常”解决方案之前我没有去查证。

I don't know what the author of that API was thinking, except that it is possible that when it was introduced, MAX_PATHreally was the largest possible path, making the correct recipe easy. Simply do all file name manipulation in a buffer of length no less than MAX_PATHcharacters.

我不知道那个 API 的作者在想什么,除了它可能在引入时MAX_PATH确实是最大的可能路径,使正确的配方变得容易。只需在长度不小于MAX_PATH字符的缓冲区中进行所有文件名操作。

Oh, yeah, don't forget that path names since 1995 or so allow Unicode characters. Because Unicode takes more room, any path name can be preceeded by \\?\to explicitly request that the MAX_PATHrestriction on its byte length be dropped for that name. This complicates the question.

哦,是的,不要忘记自 1995 年左右以来的路径名允许使用 Unicode 字符。由于 Unicode 占用更多空间,因此可以在任何路径名之前\\?\明确请求MAX_PATH取消对该名称的字节长度限制。这使问题复杂化。

MSDN has this to say about path length in the article titled File Names, Paths, and Namespaces:

MSDN 在标题为File Names, Paths, and Namespaces的文章中谈到了路径长度:

Maximum Path Length

In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_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:\<some 256 character path string><NUL>" where "<NUL>" 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 lpMaximumComponentLengthparameter of the GetVolumeInformationfunction. To specify an extended-length path, use the "\\?\" prefix. For example, "\\?\D:\<very long path>". (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 to MAX_PATHcharacters 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 exceed MAX_PATHminus 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.

最大路径长度

在 Windows API 中(以下段落中讨论的一些例外情况除外),路径的最大长度为MAX_PATH,定义为 260 个字符。本地路径按以下顺序构造:驱动器号、冒号、反斜杠、由反斜杠分隔的组件和终止的空字符。例如,驱动器 D 上的最大路径是“ D:\<some 256 character path string><NUL>”,其中“ <NUL>”表示当前系统代码页的不可见终止空字符。(<>此处使用的字符是为了视觉清晰,不能是有效路径字符串的一部分。)

注意 Windows API 中的文件 I/O 函数将“ /”转换为“ \”,作为将名称转换为 NT 样式名称的一部分,除非使用“ \\?\”前缀,如以下部分所述。

Windows API 有许多函数,这些函数也有 Unicode 版本,以允许最大总路径长度为 32,767 个字符的扩展长度路径。这种类型的路径由以反斜杠分隔的组件组成,每个组件最多为函数lpMaximumComponentLength参数中返回的值 GetVolumeInformation。要指定扩展长度的路径,请使用“ \\?\”前缀。例如,“ \\?\D:\<very long path>”。(<>此处使用的字符是为了视觉清晰,不能是有效路径字符串的一部分。)

注意 32,767 个字符的最大路径是近似值,因为“ \\?\”前缀可能在运行时被系统扩展为更长的字符串,并且这种扩展适用于总长度。

" \\?\" 前缀也可用于根据通用命名约定 (UNC) 构造的路径。要使用 UNC 指定这样的路径,请使用“ \\?\UNC\”前缀。例如,“ \\?\UNC\server\share”,其中“server”是机器的名称,“share”是共享文件夹的名称。这些前缀不用作路径本身的一部分。它们表示路径应该以最少的修改传递给系统,这意味着您不能使用正斜杠来表示路径分隔符,也不能使用句点来表示当前目录。此外,您不能将“ \\?\”前缀与相对路径一起使用,因此相对路径仅限于MAX_PATH前面针对不使用“的路径所述的字符\\?\

使用API​​创建目录时,指定路径不能太长,不能追加8.3的文件名(即目录名不能超过MAX_PATH负12)。

外壳和文件系统有不同的要求。可以使用 Windows API 创建一个 shell 用户界面可能无法处理的路径。

So an easy answer would be to allocate a buffer of size MAX_PATH, retrieve the name and check for errors. If it fit, you are done. Otherwise, if it begins with "\\?\", get a buffer of size 64KB or so (the phrase "maximum path of 32,767 characters is approximate" above is a tad troubling here so I'm leaving some details for further study) and try again.

所以一个简单的答案是分配一个 size 的缓冲区MAX_PATH,检索名称并检查错误。如果合适,你就完成了。否则,如果它以“ \\?\”开头,则获取大小为 64KB 左右的缓冲区(上面的短语“32,767 个字符的最大路径是近似值”在这里有点麻烦,因此我将留下一些细节以供进一步研究),然后再试一次。

Overflowing MAX_PATHbut not beginning with "\\?\" appears to be a "can't happen" case. Again, what to do then is a detail you'll have to deal with.

溢出MAX_PATH但不以“ \\?\”开头似乎是“不可能发生”的情况。同样,接下来要做什么是您必须处理的细节。

There may also be some confusion over what the path length limit is for a network name which begins "\\Server\Share\", not to mention names from the kernel object name space which begin with "\\.\". The above article does not say, and I'm not certain about whether this API could return such a path.

对于以“ \\Server\Share\”开头的网络名称的路径长度限制,可能还有一些混淆,更不用说以“ \\.\”开头的内核对象名称空间中的名称了。上面文章没有说,我不确定这个API是否可以返回这样的路径。

回答by Coriiander Drewitt

While the API is proof of bad design, the solution is actually very simple. Simple, yet sad it has to be this way, for it's somewhat of a performance hog as it might require multiple memory allocations. Here is some keypoints to the solution:

虽然 API 证明了糟糕的设计,但解决方案实际上非常简单。很简单,但很遗憾,它必须是这种方式,因为它可能需要多个内存分配,因此有点影响性能。以下是解决方案的一些关键点:

  • You can't really rely on the return value between different Windows-versions as it can have different semantics on different Windows-versions (XP for example).

  • If the supplied buffer is too small to hold the string, the return value is the amount of characters including the 0-terminator.

  • If the supplied buffer is large enough to hold the string, the return value is the amount of characters excluding the 0-terminator.

  • 您不能真正依赖不同 Windows 版本之间的返回值,因为它在不同的 Windows 版本(例如 XP)上可能具有不同的语义。

  • 如果提供的缓冲区太小而无法容纳字符串,则返回值是包含 0 终止符的字符数。

  • 如果提供的缓冲区足够大以容纳字符串,则返回值是不包括 0 终止符的字符数。

This means that if the returned value exactly equals the buffer size, you still don't know whether it succeeded or not. There might be more data. Or not. In the end you can only be certain of success if the buffer length is actually greater than required. Sadly...

这意味着如果返回值正好等于缓冲区大小,您仍然不知道它是否成功。可能还有更多数据。或不。最后,只有当缓冲区长度实际上大于所需时,您才能确定成功。可惜...

So, the solution is to start off with a small buffer. We then call GetModuleFileName passing the exact buffer length (in TCHARs) and comparing the return result with it. If the return result is less than our buffer length, it succeeded. If the return result is greater than or equal to our buffer length, we have to try again with a larger buffer. Rinse and repeat until done. When done we make a string copy (strdup/wcsdup/tcsdup) of the buffer, clean up, and return the string copy. This string will have the right allocation size rather than the likely overhead from our temporary buffer. Note that the caller is responsible for freeing the returned string (strdup/wcsdup/tcsdup mallocs memory).

因此,解决方案是从一个小缓冲区开始。然后我们调用 GetModuleFileName 传递确切的缓冲区长度(在 TCHARs 中)并将返回结果与其进行比较。如果返回结果小于我们的缓冲区长度,则成功。如果返回结果大于或等于我们的缓冲区长度,我们必须使用更大的缓冲区重试。冲洗并重复直到完成。完成后,我们制作缓冲区的字符串副本 (strdup/wcsdup/tcsdup),清理并返回字符串副本。该字符串将具有正确的分配大小,而不是来自临时缓冲区的可能开销。请注意,调用者负责释放返回的字符串(strdup/wcsdup/tcsdup mallocs 内存)。

See below for an implementation and usage code example. I have been using this code for over a decade now, including in enterprise document management software where there can be a lot of quite long paths. The code can ofcourse be optimized in various ways, for example by first loading the returned string into a local buffer (TCHAR buf[256]). If that buffer is too small you can then start the dynamic allocation loop. Other optimizations are possible but that's beyond the scope here.

请参阅下面的实现和使用代码示例。我已经使用此代码十多年了,包括在企业文档管理软件中,其中可能有很多很长的路径。代码当然可以通过各种方式进行优化,例如首先将返回的字符串加载到本地缓冲区中(TCHAR buf[256])。如果该缓冲区太小,则可以启动动态分配循环。其他优化也是可能的,但这超出了这里的范围。

Implementation and usage example:

实现和使用示例:

/* 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\n", path);

    free(path); /* GetMainModulePath malloced memory using _tcsdup */ 

    return 0;
}

Having said all that, I like to point out you need to be very aware of various other caveats with GetModuleFileName(Ex). There are varying issues between 32/64-bit/WOW64. Also the output is not necessarily a full, long path, but could very well be a short-filename or be subject to path aliasing. I expect when you use such a function that the goal is to provide the caller with a useable, reliable full, long path, therefor I suggest to indeed ensure to return a useable, reliable, full, long absolute path, in such a way that it is portable between various Windows-versions and architectures (again 32/64-bit/WOW64). How to do that efficiently is beyond the scope here.

说了这么多,我想指出您需要非常了解 GetModuleFileName(Ex) 的各种其他警告。32/64 位/WOW64 之间存在不同的问题。此外,输出不一定是完整的长路径,但很可能是短文件名或受到路径别名的影响。我希望当你使用这样一个函数时,目标是为调用者提供一个可用的、可靠的、完整的、长的路径,因此我建议确实确保以这样的方式返回一个可用的、可靠的、完整的、长的绝对路径它可以在各种 Windows 版本和体系结构(同样是 32/64 位/WOW64)之间移植。如何有效地做到这一点超出了这里的范围。

While this is one of the worst Win32 APIs in existance, I wish you alot of coding joy nonetheless.

虽然这是现有的最糟糕的 Win32 API 之一,但我还是希望您能享受很多编码乐趣。

回答by Alan

My example is a concrete implementation of the "if at first you don't succeed, double the length of the buffer" approach. It retrieves the path of the executable that is running, using a string (actually a wstring, since I want to be able to handle Unicode) as the buffer. To determine when it has successfully retrieved the full path, it checks the value returned from GetModuleFileNameWagainst the value returned by wstring::length(), then uses that value to resize the final string in order to strip the extra null characters. If it fails, it returns an empty string.

我的例子是“如果一开始你没有成功,就把缓冲区的长度加倍”方法的具体实现。它使用字符串(实际上是 a wstring,因为我希望能够处理 Unicode)作为缓冲区检索正在运行的可执行文件的路径。为了确定它何时成功检索了完整路径,它会根据 返回的值检查 from 返回GetModuleFileNameW的值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 = GetModuleFileNameW(NULL, &ret[0], bufferSize);
        if (charsReturned < ret.length())
        {
            ret.resize(charsReturned);
            return ret;
        }
        else
        {
            bufferSize *= 2;
        }
    }
    return L"";
}

回答by user877329

Using

使用

extern char* _pgmptr

might work.

可能工作。

From the documentation of GetModuleFileName:

从 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 会自动初始化为可执行文件的全路径,可用于检索可执行文件的全路径名。

But if I read about _pgmptr:

但如果我读到 _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 可能被初始化为程序名(文件的基本名称,没有文件扩展名)或文件名、相对路径或完整路径。

Anyone who knows how _pgmptr is initialized? If SO had support for follow-up questions I would posted this question as a follow up.

有谁知道 _pgmptr 是如何初始化的?如果 SO 支持后续问题,我会将此问题作为后续问题发布。

回答by geniuss99

Here is a another solution with std::wstring:

这是 std::wstring 的另一个解决方案:

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

回答by zenden2k

Windows cannot handle properly paths longer than 260 characters, so just use MAX_PATH. You cannot run a program having path longer than MAX_PATH.

Windows 无法正确处理超过 260 个字符的路径,因此只需使用 MAX_PATH。您不能运行路径长于 MAX_PATH 的程序。

回答by Noxoreos

My approach to this is to use argv, assuming you only want to get the filename of the running program. When you try to get the filename from a different module, the only secure way to do this without any other tricks is described already, an implementation can be found here.

我的方法是使用 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 = 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] = '##代码##';

// do work here
// variable 'pszCompleteFilePath' contains always the complete path now

// cleanup
delete[] pszCompleteFilePath;
pszCompleteFilePath = NULL;

I had no case where argv didn't contain the file path (Win32 and Win32-console application), yet. But just in case there is a fallback to a solution that has been described above. Seems a bit ugly to me, but still gets the job done.

我还没有遇到 argv 不包含文件路径(Win32 和 Win32 控制台应用程序)的情况。但以防万一有上述解决方案的后备。对我来说似乎有点难看,但仍然可以完成工作。