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

时间:2009-04-30 07:45:13

标签: windows winapi getmodulefilename

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

如何确定保存GetModuleFileName()的整个文件名所需的实际缓冲区大小?

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

(使用零作为缓冲区大小的技巧对这个API不起作用 - 我之前已经尝试过了)

8 个答案:

答案 0 :(得分:10)

答案 1 :(得分:9)

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

答案 2 :(得分:2)

虽然API是糟糕设计的证明,但解决方案实际上非常简单。简单而又悲伤必须以这种方式,因为它可能需要多次内存分配。以下是该解决方案的一些关键点:

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

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

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

这意味着如果返回的值与缓冲区大小完全相等,您仍然不知道它是否成功。可能会有更多数据。或不。最后,如果缓冲区长度实际上大于要求,则只能确定成功。不幸的是...

因此,解决方案是从一个小缓冲区开始。然后,我们调用GetModuleFileName传递确切的缓冲区长度(在TCHAR中)并将返回结果与它进行比较。如果返回结果小于我们的缓冲区长度,则成功。如果返回结果大于或等于我们的缓冲区长度,我们必须再次尝试使用更大的缓冲区。冲洗并重复直至完成。完成后,我们制作缓冲区的字符串副本(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 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;
}

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

虽然这是存在的最糟糕的Win32 API之一,但我希望你能有很多编码的快乐。

答案 3 :(得分:1)

使用

extern char* _pgmptr

可能有用。

来自GetModuleFileName的文档:

  

全局变量_pgmptr自动初始化为可执行文件的完整路径,可用于检索可执行文件的完整路径名。

但如果我读到_pgmptr:

  

当程序未从命令行运行时,_pgmptr可能会初始化为程序名称(文件的基本名称没有文件扩展名)或文件名,相对路径或完整路径。

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

我的例子是&#34的具体实现;如果一开始你没有成功,加倍缓冲区的长度&#34;做法。它使用字符串(实际上是wstring,因为我希望能够处理Unicode)作为缓冲区来检索正在运行的可执行文件的路径。要确定何时成功检索完整路径,它会检查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"";
}

答案 6 :(得分:0)

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

答案 7 :(得分:-2)

我的方法是使用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] = '\0';

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

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

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