Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Win32 CreateProcess: When is CREATE_UNICODE_ENVIRONMENT *really* needed?

The CreateProcess documentation states (my bold emphasis):

lpEnvironment [in, optional]

[...] If the environment block pointed to by lpEnvironment contains Unicode characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT. If this parameter is NULL and the environment block of the parent process contains Unicode characters, you must also ensure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.

Is MSDN wrong and overstating the flag's meaning or is that a real requirement?

I've seen code which never sets the flag and appears to work but the paranoid part of me wants to comply 100% with what MSDN says. Saying that, I'm not sure you really can follow MSDN's rules without going to extremes.

Having to set (or not set) CREATE_UNICODE_ENVIRONMENT when lpEnvironment is NULL strikes me as ridiculous:

  1. If I don't pass an environment block then CreateProcess must obtain the block itself. In that case it's in a better position than I am to know the block's type.

  2. How do I know if the block actually contains Unicode characters?

    Am I expected to get the block and inspect it for characters outside the current code-page? (I assume that's what MSDN means by "Unicode characters" here.)

    If I do have to obtain the env-block then I might as well pass it in lpEnvionment instead of NULL, so why even allow NULL?

    Having to obtain and inspect the env-block seems an insane requirement for every caller of CreateProcess; it's surely something the API itself should handle.

  3. When it says "parent process" does it even mean my process, that's about to become a new parent, or does it mean my process's parent? My initial reading of MSDN made me think I had to somehow tell if the CreateProcess call which started my process had been passed an ANSI or Unicode environment block, but that surely isn't the case.

    I'm assuming, on NT-based OS, that all processes have a Unicode env block, converted from ANSI if required at process creation, and that processes don't hang on to whatever block of data was passed to CreateProcess as-is.

    (Maybe this whole thing is a left-over from the Win9x days where the OS itself wasn't Unicode? Even then, though, I can't see how application code could make the decision better than the OS itself, nor why it should be expected to.)

  4. As well as code which never sets the flag, I've seen code which always sets it if UNICODE was defined at compile time. That makes no sense when the requirement is on what's in the env block at runtime and when the code could be in a DLL loaded into a foreign process.

    The env block is process wide so UNICODE being defined at compile-time seems irrelevant.

  5. If it's just a matter of whether I call CreateProcessA or CreateProcessW then the flag should be implicit when the block is NULL, so that doesn't make sense either.

In my own code I decided to avoid the question and always obtain a Unicode copy of the environment block (via GetEnvironmentStringsW), always pass it to CreateProcess and always set CREATE_UNICODE_ENVIRONMENT. That is the only way I can see to be 100% correct based on what MSDN says.

Surely what I'm doing is redundant, though. CreateProcess can't be that stupid, can it?

On the other hand, this is CreateProcess we're talking about. It isn't the best-designed API and has a bunch of other pitfalls (off the top of my head):

  1. Failing if the argument string is const because it modifies it in-place.
  2. Making the first argument optional and thus inviting people to forget to quote the exe-path in the second argument.
  3. Requiring the properly-quoted exe-path in the second argument even if it is given explicitly in the first.

So perhaps it isn't right to assume it behaves intelligently or is likely to take care of chores for the caller...

I don't know whether to remove the paranoid junk from my own code or add it to all the other code I see. Argh. :-)

ADDED 18/Nov/2010:

It certainly seems like the flag is irrelevant when the env-block is NULL, at least in Windows 2000 through to Windows 7. See my test below.

Obviously this doesn't make it conclusive that the flag will always be irrelevant in all future OS, but I really can't see how it could be otherwise.

Say we have Grandparent which created Parent which is about to create Child:

  • If the OS always stores the env-block for Parent as Unicode -- having converted it from ANSI during Parent's creation if Grandparent passed an ANSI block -- then CreateProcess would be in error to pay any attention to the flag when Parent passes a NULL block. CreateProcess must KNOW the block Child will inherit will ALWAYS be Unicode.

  • Alternatively, OS may store the env-block for Parent exactly as it came from Grandparent. (This seems unlikely but it's possible.) In that case, Parent has no way to detect what type of block Grandparent passed. Again, CreateProcess must know the block's type and ignore the flag.

Here's a test I wrote this morning that launches a child process in different ways and makes the child process report an env-var (just the "OS" variable for brevity):

wchar_t *szApp = L"C:\\Windows\\system32\\cmd.exe";
wchar_t *szArgs = L"\"C:\\Windows\\system32\\cmd.exe\" /C set OS";
STARTUPINFOW si = {0};
si.cb = sizeof(si);
PROCESS_INFORMATION pi = {0};

// For brevity, this leaks the env-blocks and thread/process handles and doesn't check for errors.
// Must compile as non-Unicode project, else GetEnvironmentStringsA is hidden by WinBase.h
for(int i = 0; i < 3; ++i)
{
    const char *t = (i==0) ? "no env" : (i==1) ? "unicode env" : "ansi env";
    void *env = (i==0) ? NULL : (i==1) ? (void*)GetEnvironmentStringsW() : (void*)GetEnvironmentStringsA();
    printf("--- %s / unicode flag ---\n", t, i);
    ::CreateProcessW(szApp, szArgs, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, env, NULL, &si, &pi);
    ::WaitForSingleObject(pi.hProcess, INFINITE);
    printf("\n--- %s / ansi flag ---\n", t, i);
    ::CreateProcessW(szApp, szArgs, NULL, NULL, FALSE, 0, env, NULL, &si, &pi);
    ::WaitForSingleObject(pi.hProcess, INFINITE);
    printf("\n");
}

This outputs:

--- no env / unicode flag ---
OS=Windows_NT

--- no env / ansi flag ---
OS=Windows_NT

--- unicode env / unicode flag ---
OS=Windows_NT

--- unicode env / ansi flag ---

--- ansi env / unicode flag ---

--- ansi env / ansi flag ---
OS=Windows_NT

When the env-block is NULL the flag doesn't matter there.

When it isn't NULL the flag does matter, since CreateProcess needs to be told what's behind the void* (but that's obvious and the question is purely about the NULL case).

Can anyone think of any scenario at all where the flag could possibly matter when the env-block is NULL? And in that scenario, how would the app possibly know the correct value of the flag better than the OS itself?

like image 278
Leo Davidson Avatar asked Nov 17 '10 15:11

Leo Davidson


1 Answers

Note that in the declaration of the CreateProcess function the lpEnvironment parameter is declared is LPVOID.

What does this mean? It means that you may use Ansi/Unicode version of CreateProcess function and pass it Ansi/Unicode version environment block in any combination. In particular you may use Unicode version of CreateProcess and pass it Ansi environment block and vice versa.

So that setting the CREATE_UNICODE_ENVIRONMENT is required iff you actually use unicode environment block, because there's no other conventional way (apart from some ugly heuristics) the OS may realize it's unicode.

Now regarding your questions:

  1. If you don't pass the environment block explicitly - the newly created process will initially have the same environment variables as its creator. Unless you need to make some extra-configuration to the newly created process - nothing more than this is reqiured.

  2. If you pass the environment block to the newly created process - you must either manually construct it or get it from somewhere. In either way you must know if it's unicode.

  3. Parent of the new process is its creator. In your particular case - your process.

  4. This depends solely on how environment block is created. If you always pass what you get by calling GetEnvironmentStrings - then it's in unicode iff you're compiling with UNICODE defined. Then you should set CREATE_UNICODE_ENVIRONMENT iff you're compiling in unicode. On the other hand if you construct it manually - you may construct it in unicode even if you don't compiler in unicode. Hence - you should set CREATE_UNICODE_ENVIRONMENT according to how you constructed the environment block, not according to the compilation defines.

  5. As said already, both CreateProcessA and CreateProcessW may work with either Ansi or Unicode environment block. This is exactly the reason why this flag is required.

like image 145
valdo Avatar answered Nov 15 '22 19:11

valdo