ExpandEnvironmentStringsA returns a different required buffer length than ExpandEnvironmentStringsW

I was writing some ANSI-only code the other day to handle the case where an environment string expansion could return a buffer larger than MAX_PATH (the test code I was modifying assumed that it could only ever see a MAX_PATH output, I wanted to make it more resilient).

The way the code worked, I defined a wrapper for ExpandEnvironmentStringsA that returned a std:::string and then appended values to that string. But I found that my strings weren’t working correctly – none of the appended values were actually being appended.

Digging in, I discovered that there was an embedded null in the string, which didn’t make any sense to me – where did that come from?

Here’s the wrapper I wrote for the call to ExpandEnvironmentStrings:

std::string ExpandEnvironmentStringsToString(PCSTR sourceString)
    DWORD dwEvSize;
    dwEvSize = ExpandEnvironmentStrings( sourceString, nullptr, 0);
    if (dwEvSize == 0)
        return std::string();
        std::string returnValue(dwEvSize, L'\0');
        dwEvSize = ExpandEnvironmentStrings( sourceString, &returnValue[0], dwEvSize);
        if (dwEvSize == 0)
            return std::string();
        returnValue.resize(dwEvSize-1); // dwEvSize returned by ExpandEnvironmentStrings includes the trailing null, truncate it.
        return returnValue;

That code looks like it should be just fine, but there was still that unexpected extra null character.

On a lark, I switched the code to Unicode and tested the version that returned an std::wstring. That worked perfectly – the string converted the string perfectly.

Since ExpandEnvironmentStringsW worked perfectly and ExpandEnvironmentStringsA added an embedded null, I started looking at the return value of ExpandEnvironmentStringsA. It turns out that ExpandEnvironmentStringsA always returned enough space for *two* null characters, not the one character it’s documented as requiring.

Once I figured that out, the solution to my problem was clear. Just change the




to account for the additional null character.

Just another day in the strange world that is the Win32 API surface Smile.

Comments (8)

  1. Dominic Self says:

    I think you have a typo in your 'after' code, as currently is the same as the 'before' code 🙂

  2. Bak says:

    Yes, there's a typo, I think that second line should be -2 instead of -1.

    Btw, nice to see that Larry is alive!!

  3. otstrel says:

    Is it only me or &returnValue[0] expression actually looks a bit too implementation specific?..

  4. @otstrel: Funny, that's the next blog post I want to write :).

  5. Klimax says:

    Isn't that pre-C++11 way of getting pointer to raw buffer? (Now it is just data() or c_str() – both being const for basic_string)

  6. @Klimax: Because both data() and c_str() return const strings, they can't be modified. &string[0] returns a non const buffer.

  7. Klimax says:

    Didn't realize it is non-const. Its presence then makes sense. (Didn't need it so far, even when writing wrappers for C-API like HTTP Server…)

    Another bit learned to day. Thanks.

Skip to main content