A brief history of the GetEnvironmentStrings functions


The Get­Environment­Strings function has a long and troubled history.

The first bit of confusion is that the day it was introduced in Windows NT 3.1, it was exported funny. The UNICODE version was exported under the name Get­Environment­StringsW, but the ANSI version was exported under the name Get­Environment­Strings without the usual A suffix.

A mistake we have been living with for over two decades.

This is why the winbase.h header file contains these confusing lines:

WINBASEAPI
LPCH
WINAPI
GetEnvironmentStrings(
    VOID
    );

WINBASEAPI
LPWCH
WINAPI
GetEnvironmentStringsW(
    VOID
    );

#ifdef UNICODE
#define GetEnvironmentStrings  GetEnvironmentStringsW
#else
#define GetEnvironmentStringsA  GetEnvironmentStrings
#endif // !UNICODE

It's trying to clean up a mess that was created long ago, and it only partly succeeds. This is why your IDE may get confused when you try to call the Get­Environment­Strings function and send you to the wrong definition. It's having trouble untangling the macros whose job is to try to untangle the original mistake.

The kernel folks tried to clean this up as quickly as they could, by exporting new functions with the names Get­Environment­StringsW and Get­Environment­StringsA, like they should have been in the first place, but for compatibility purposes, they still have to export the weird unsuffixed Get­Environment­Strings function. And then to avoid all the "gotcha!"s from people looking for proof of nefarious intent, they kept the mistake in the public header files to make their actions visible to all.

Though personally, I would have tidied things up differently:

WINBASEAPI
LPCH
WINAPI
GetEnvironmentStrings(
    VOID
    );

WINBASEAPI
LPCH
WINAPI
GetEnvironmentStringsA(
    VOID
    );

WINBASEAPI
LPWCH
WINAPI
GetEnvironmentStringsW(
    VOID
    );

#ifdef UNICODE
#define GetEnvironmentStrings  GetEnvironmentStringsW
#else
#define GetEnvironmentStrings  GetEnvironmentStringsA
#endif // !UNICODE

I would have left the declaration of the mistaken Get­Environment­Strings function in the header file, but redirected the symbolic name to the preferred suffixed version.

But then again, maybe my version would have confused IDEs even more than the current mechanism does.

The other unfortunate note in the history of the Get­Environment­Strings function is the odd way it handled the Unicode environment. Back in the old days, the Get­Environment­Strings function returned a raw pointer to the environment block. The result was that if some other code modified the environment, your pointer became invalid, and there was nothing you could do about it. As I noted, the function was subsequently changed so that both the ANSI and Unicode versions return snapshots of the environment strings, so that the environment strings you received wouldn't get spontaneously corrupted by another thread.

Comments (24)
  1. WndSks says:

    Both kernel32 (lstr*) and shell32 also contain other A/W/plain versions of some functions.

  2. Maurits says:

    Speaking of environment variables, how does one get the user-specific portion of PATH as a limited user?

    When I go to the System control panel I get auto-elevated so I'm seeing only the machine portion and the admin user's portion.

    I can set the user-specific portion via setx, and I can get the combined machine + user portion via %PATH%.

    As a workaround I'm setting the user-specific portion once to an unexpanded environment variable (%userpath%) and then manipulating that.

    But I'm wondering if I'm missing a more elegant solution.

  3. RS783 says:

    Hang on a minute – you said this was introduced in NT 3.1 (and I'm sure you're right), but if I click your hyperlink (GetEnvironmentStrings function) it says Windows XP is the minimum supported client for desktop apps (which seems absurd).

  4. Joshua says:

    Wow induced memory leak for anybody expecting it to be the old way.

    [Read the linked article. No memory leak for people who followed the rules. -Raymond]
  5. SimonRev says:

    RS783 — if you spelunk around MSDN enough you will notice that when they touch the docs for a function they update the minimum supported client line to match whatever is currently at the bottom of the support totem pole.  That way they can keep stuff that is Windows 3 specific from cluttering up the docs.  They are not perfectly consistent about it (you can find a lot of functions which discuss idiosyncrasies on Win95/98, but they become fewer every year)

  6. John says:

    @RS783:  The online MSDN documentation is apparently being scrubbed, only listing operating systems currently supported by Microsoft.  There are some sections that still list Windows 2000, though.  This seems kind of pointless to me because it's actually more work to modify the documentation than to just leave it as is.  Probably some kind of legal thing.

  7. SimonRev says:

    @John — I am happy it gets scrubbed.  Do you really want to read about Microsoft Layer For Unicode on every single function that comes in A/W variations, because it was important to Win95/98?

    I think my head would explode if the docs were still full of 16 bit programming concerns.

  8. RS783 says:

    I took "minimum supported client" to mean "minimum client version that supports the feature" but maybe the emphasis is actually on the word "supported".  Perhaps MSDN's interpretation of the phrase is actually more literal than mine was.

    Thanks guys.

  9. rsola says:

    Maurits, typing "environment" in the Start menu search box, or in the Control Panel search box, takes you to "Edit environment variables for your account" or "Edit the system environment variables". Actually, typing "env" should be enough. "Edit environment variables for your account" runs Rundll32.exe sysdm.cpl,EditEnvironmentVariables and it does not require elevation. You might like to create a shortcut for this.

    Also, you can find the task "Change my environment variables" in Control Panel, User Accounts and Family Safety, User Accounts. I have taken Windows 7 with the English language pack as a reference. Steps for other Windows versions and languages will be somewhat different.

  10. Paul Parks says:

    @Maurits: To edit my user-specific path in Windows 8 (similar in 7/Vista) I press CTRL-Break to bring up the System control panel, then click "Advanced system settings" on the left-hand side, then click the "Advanced" tab in the "System Properties" dialog, then click the "Environment Variables" button. User variables are listed in the top list, system variables at the bottom. It even says, "User variables for Paul," my non-admin user, above the top list. The elevation doesn't change the active user.

  11. Maurits says:

    Thanks @Ramón Sola and Paul M. Parks.

  12. Joshua says:

    @Simon Rev: The only thing I wish they kept, was for each API, structure, flag constant, the minimum Windows version required to use it. Obviously, all the 16 bit stuff wouldn't be there as this is the 32 & 64 bit documentation.

  13. The most confusing case I know of is ILCreateFromPath in SHELL32.  It's exported as A/W/plain and the plain version is actually the Unicode version!

  14. dbacher says:

    @Raymond,

    Is this actually a mistake or does it merely predate the A/W convention?

    That is — pretend that you are starting work on the kernel.  In order to run the compiler, you'd need this — some of the other API's WndSks mentioned, etc. — right?  Can't see a way for the compiler environment to work without this particular API.

    And so at that point, you might still be arguing a TUCOWS-like approach with separate libraries.  If you have separate libraries, then you could argue for a Windows-Ansi.h and a Windows-Unicode.h, or a similar approach, pretty easily — and so conceptually it could be seen that maybe there were some architectural arguments going on.  The folks writing the kernel wouldn't necessarily want to wait for those debates to be settled (especially since they still happen routinely in mailing lists, forums, etc. today), and so they'd move ahead with the development to get something that works while the matter is settled.

    Later on, someone would have to go clean this up — which would potentially break tools.  And so you'd be stuck with both entry points permanently, if you didn't make the decision very early in the process.

  15. Georg Rottensteiner says:

    @SimonRev: I actually do not like the clean up job running on MSDN docs. It's nigh impossible now to find out which functions are available on which Windows versions. That's a real problem if you need to support older Windows versions and want to make sure your app behaves right.

  16. ThomasX says:

    Why not simply fix the original mistake?

  17. Matt says:

    @ThomasX: Because then any binary compiled to import kernel32!GetEnvironmentStrings will fail on the next version of Windows.

  18. Maurits says:

    Looks like the user-specific portion of the PATH environment variable is just HKCUEnvironmentPath

    technet.microsoft.com/…/cc978681.aspx

  19. John Doe says:

    @Maurits, but then you'll have to log off and log on to see the effect.

    Using the environment variables dialog, which runs as its own dialog from Windows Vista onwards ("Change my environment variables"), the changes will take effect on new processes after pressing OK or Apply.

    The registry thing is good to know for setting variables on a newly created or logged off user, using HKU<SID> instead of HKCU, although the settings from the default profile should be enough for most cases.

  20. @Maurits: If you need to propagate these changes without a reboot/logoff-login, read this: support.microsoft.com/…/104011

    I'm assuming you know about RegCloseKey and RegFlushKey.

  21. mikeb says:

    @Dave Bacher: "a TUCOWS-like approach with separate libraries"

    What does "TUCOWS" mean here?  The shareware download site doesn't seem to fit.

  22. GregM says:

    Mike, my guess is that Dave meant MSLU (Microsoft Layer for Unicode).

  23. Richard Cranium says:

    Which was also known as UNICOWS, and hence why he got the two confused.

  24. GregM says:

    Richard, thanks, I thought there was something similar to that, but I couldn't find it.

Comments are closed.