c++winapiwindows-xpgetmodulefilename

Unclear edge-case of GetModuleFileName


MSDN docs state:

lpFilename [out]

A pointer to a buffer that receives the fully qualified path of the module. If the length of the path is less than the size that the nSize parameter specifies, the function succeeds and the path is returned as a null-terminated string.

If the length of the path exceeds the size that the nSize parameter specifies, the function succeeds and the string is truncated to nSize characters including the terminating null character.

Windows XP: The string is truncated to nSize characters and is not null-terminated.

This is ambiguous. Am I to interpret this that the string is never null-terminated on Windows XP? Or is it only not null-terminated when the string gets truncated?

I would appreciate it if someone knows of a better-worded reference, or is running Windows XP somewhere and can simply test the behavior.


Solution

  • I think you need to read that line in the context of the para above:

    If the function succeeds, the return value is the length of the string that is copied to the buffer, in characters, not including the terminating null character. If the buffer is too small to hold the module name, the string is truncated to nSize characters including the terminating null character, the function returns nSize, and the function sets the last error to ERROR_INSUFFICIENT_BUFFER.

    Windows XP: If the buffer is too small to hold the module name, the function returns nSize. The last error code remains ERROR_SUCCESS. If nSize is zero, the return value is zero and the last error code is ERROR_SUCCESS.

    If the function fails, the return value is 0 (zero). To get extended error information, call GetLastError.

    And then use a combination of the return value and a call to GetLastError() to determine whether you have a complete path.

    If you have a complete path (ERROR_SUCCESS) AND return-value == nSize then you ought not assume that it's null-terminated.

    In my view, this argues for never assuming that it's null-terminated. The interface is broken. The buffer you send to the function should be one char bigger than nSize. Then you can null-terminate.

    As of c++11, std::basic_string<TCHAR> is guaranteed to append a trailing zero, so something like this should do nicely:

    std::basic_string<TCHAR> better_get_module_filename(HMODULE hModule)
    {
        std::basic_string<TCHAR> result(128, 0);
        DWORD err = ERROR_SUCCESS;
        do {
            auto actual = GetModuleFileName(hModule, std::addressof(result[0]), result.size());
            err = GetLastError();
            if (actual == 0) {
                throw "error of choice, wrapping err";
            }
            result.resize(actual);
        } while(err == ERROR_INSUFFICIENT_BUFFER);
        return result;
    }