代码之家  ›  专栏  ›  技术社区  ›  Francis

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

  •  11
  • Francis  · 技术社区  · 15 年前

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

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

    大多数人使用 MAX_PATH 但我记得路径可以超过这个值(默认定义是260)。

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

    7 回复  |  直到 7 年前
        1
  •  9
  •   sharptooth    15 年前

    执行一些合理的策略来增加缓冲区,比如从max_path开始,然后使每个连续的大小比前一个大1.5倍(或者对于较少的迭代大2倍)。迭代直到函数成功。

        2
  •  10
  •   RBerteig Keith Adler    15 年前

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

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

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

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

    我不知道那个API的作者在想什么,只是当它被引入时, 最大路径 真的是最大的可能路径,使正确的食谱容易。只需在长度不小于 最大路径 字符。

    哦,是的,不要忘记自1995年以来的路径名允许使用Unicode字符。因为unicode占用了更多的空间,任何路径名都可以被 \\?\ 明确要求 最大路径 对该名称的字节长度的限制将被删除。这使问题复杂化了。

    在标题为 File Names, Paths, and Namespaces :

    最大路径长度

    在Windows API中 以下讨论的例外情况 段落),a的最大长度 路径是 最大路径 ,定义为 260个字符。本地路径是 结构顺序如下: 驱动器号、冒号、反斜杠, 用反斜杠隔开的部件, 以及一个终止的空字符。为了 例如,驱动器D上的最大路径 是 D:\<some 256 character path string><NUL> “哪里” <NUL> “代表” 不可见终止空值 当前系统的字符 代码页。(人物) < > 被使用 为了清晰可见,不能 有效路径字符串的一部分。)

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

    Windows API有许多功能 也有Unicode版本 允许为 最大路径总长32767 字符。这种类型的路径是 由以下部件组成: 反斜杠,每个反斜杠的值 归还 lpMaximumComponentLength 参数 这个 GetVolumeInformation 功能。到 指定扩展长度路径,使用 “ \?\ “前缀”。例如, “ \\?\D:\<very long path> “。(The 文字 < > 用于 视觉清晰,不能是 有效的路径字符串。)

    注意最大路径为32767 字符是近似的,因为 “ \?\ “前缀可以扩展为 系统运行时的较长字符串 时间,这个扩展适用于 总长度。

    \?\ “也可以使用前缀 根据 通用命名约定(UNC)。 要使用UNC指定此类路径,请使用 “ \\?\UNC\ “前缀”。例如, “ \\?\UNC\server\share ,其中“服务器” 是机器的名称和“共享” 是共享文件夹的名称。 这些前缀不作为 路径本身。它们表明 路径应传递到 系统修改最少, 这意味着你不能使用 正斜杠表示路径 分隔符,或表示的句点 当前目录。还有,你 无法使用“ \?\ 带前缀的 相对路径,因此相对 路径限制为 最大路径 如前所述的字符 路径不使用“ \?\ “前缀”。

    使用API创建 目录,指定的路径不能 长到不能附加 8.3文件名(即目录名不能超过 最大路径 减12)。

    shell和文件系统 不同的要求。这是可能的 使用Windows API创建路径 shell用户界面可能 无法处理。

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

    溢流 最大路径 但不是以“开头” \?\ “似乎是“不可能发生”的情况。再说一遍,接下来要做的是你必须处理的一个细节。

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

        3
  •  2
  •   Coriiander Drewitt    9 年前

    虽然API是糟糕设计的证明,但解决方案实际上非常简单。很简单,但也很遗憾,必须这样做,因为它可能需要多个内存分配,这在某种程度上是一种性能消耗。以下是解决方案的一些关键点:

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

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

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

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

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

    请参阅下面的实现和使用代码示例。我已经使用这个代码十多年了,包括在企业文档管理软件中,在那里有很多很长的路要走。当然,可以通过各种方式优化代码,例如,首先将返回的字符串加载到本地缓冲区(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之一,但我还是希望您能享受编码的乐趣。

        4
  •  1
  •   user877329    11 年前

    使用

    extern char* _pgmptr
    

    可能奏效。

    从getmodulefilename的文档中:

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

    但是,如果我读到关于pgmtpr的文章:

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

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

        5
  •  0
  •   zenden2k    9 年前

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

        6
  •  0
  •   Alan jiggunjer    7 年前

    我的例子是“如果一开始你不成功,将缓冲区的长度增加一倍”方法的具体实现。它使用字符串(实际上是 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"";
    }
    
        7
  •  -2
  •   Noxoreos    9 年前

    我的方法是使用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控制台应用程序)的情况。但为了防止出现上述解决方案的倒退。在我看来有点难看,但还是能完成任务。