Win32 CreateProcess:什么时候*真正*需要CREATE_UNICODE_ENVIRONMENT?

发布于 2024-10-03 03:41:41 字数 4265 浏览 3 评论 0原文

CreateProcess 文档指出(我粗体强调) :

lpEnvironment [in,可选]

[...] 如果 lpEnvironment 指向的环境块包含 Unicode 字符,请确保 dwCreationFlags 包含 CREATE_UNICODE_ENVIRONMENT。 如果该参数为NULL,并且父进程的环境块包含Unicode字符,则还必须确保dwCreationFlags包含CREATE_UNICODE_ENVIRONMENT。

MSDN 是否错误并夸大了该标志的含义,或者这是真正的要求吗?

我见过一些代码从未设置过标志并且似乎可以工作,但我偏执的部分想要 100% 遵守 MSDN 所说的。话虽如此,我不确定您是否真的可以遵循 MSDN 的规则而不走极端。

当 lpEnvironment 为 NULL 时必须设置(或不设置)CREATE_UNICODE_ENVIRONMENT 让我觉得很荒谬:

  1. 如果我不传递环境块,那么 CreateProcess 必须获取该块本身。在这种情况下,它比我更能了解块的类型。

  2. 我如何知道该块是否确实包含 Unicode 字符?

    我是否期望获取该块并检查它是否有当前代码页之外的字符? (我认为这就是 MSDN 中“Unicode 字符”的含义。)

    如果我确实必须获取环境块,那么我不妨将它传递到 lpEnvionment 而不是 NULL,那么为什么还要允许 NULL?

    对于 CreateProcess 的每个调用者来说,必须获取并检查环境块似乎是一个疯狂的要求;这肯定是 API 本身应该处理的事情。

  3. 当它说“父进程”时,它是否意味着我的进程(即将成为新的父进程),或者它是否意味着我的进程的父进程?我最初阅读 MSDN 时觉得我必须以某种方式判断启动我的进程的 CreateProcess 调用是否已经传递了 ANSI 或 Unicode 环境块,但事实并非如此。

    我假设,在基于 NT 的操作系统上,所有进程都有一个 Unicode 环境块,如果在进程创建时需要,则从 ANSI 转换,并且进程不会挂在传递给 CreateProcess 的任何数据块上-是。

    (也许这整件事是 Win9x 时代遗留下来的,当时操作系统本身不是 Unicode?尽管如此,我还是看不出应用程序代码如何能够比操作系统本身做出更好的决定,也不明白为什么它应该被期望。)

  4. 除了从不设置该标志的代码之外,我还见过如果在编译时定义了 UNICODE 则始终设置该标志的代码。当要求是运行时 env 块中的内容以及代码可能位于加载到外部进程的 DLL 中时,这是没有意义的。

    env 块是进程范围的,因此在编译时定义 UNICODE 似乎无关紧要。

  5. 如果这只是我是否调用 CreateProcessA 还是 CreateProcessW 的问题,那么当块为 NULL 时,该标志应该是隐式的,因此这也没有意义。

在我自己的代码中,我决定避免这个问题,并始终获取环境块的 Unicode 副本(通过 GetEnvironmentStringsW),始终将其传递给 CreateProcess 并始终设置 CREATE_UNICODE_ENVIRONMENT。根据 MSDN 的说法,这是我认为 100% 正确的唯一方法。

当然,我所做的事情是多余的。 CreateProcess 不会那么愚蠢吧?

另一方面,这就是我们正在讨论的CreateProcess。它不是设计最好的 API,并且还有很多其他陷阱(超出了我的想象):

  1. 如果参数字符串是 const,则会失败,因为它会就地修改它。
  2. 将第一个参数设置为可选,从而导致人们忘记在第二个参数中引用 exe 路径。
  3. 即使在第一个参数中明确给出,也需要在第二个参数中正确引用 exe 路径。

因此,也许假设它表现得很聪明或者可能会为调用者处理杂务是不正确的......

我不知道是否要从我自己的代码中删除偏执的垃圾,或者将其添加到我的所有其他代码中看。啊。 :-)

添加于 2010 年 11 月 18 日:

当环境块为 NULL 时,该标志显然是无关紧要的,至少在 Windows 2000 到 Windows 7 中是这样。请参阅下面的测试。

显然,这并不能得出结论,该标志在所有未来的操作系统中始终无关紧要,但我真的看不出它怎么可能是其他情况。

假设我们有祖父母,它创建了即将创建子对象的父对象:

  • 如果操作系统始终将父对象的环境块存储为 Unicode - 在创建父对象期间将其从 ANSI 转换,如果祖父母传递了 ANSI 块 - 那么 CreateProcess当 Parent 传递 NULL 块时,注意该标志将是错误的。 CreateProcess 必须知道子级将继承的块始终是 Unicode。

  • 或者,操作系统可以准确地存储来自祖父母的环境块。 (这似乎不太可能,但有可能。)在这种情况下,Parent 无法检测 Grandparent 通过的块类型。同样,CreateProcess 必须知道块的类型并忽略该标志。

这是我今天早上写的一个测试,它以不同的方式启动一个子进程,并使子进程报告一个 env-var(为了简洁起见,只是“OS”变量):

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");
}

输出:

--- 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

当 env-block 为 NULL 时,标志不会'那里没关系。

当它不为 NULL 时,该标志确实很重要,因为 CreateProcess 需要被告知 void* 后面的内容(但这很明显,问题纯粹是关于 NULL 的情况)。

任何人都可以想到当环境块为 NULL 时标志可能起作用的任何场景吗?在这种情况下,应用程序如何可能比操作系统本身更好地知道标志的正确值?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

怎会甘心 2024-10-10 03:41:41

请注意,在 CreateProcess 函数的声明中,lpEnvironment 参数声明为 LPVOID

这意味着什么?这意味着您可以使用 CreateProcess 函数的 Ansi/Unicode 版本,并以任意组合向其传递 Ansi/Unicode 版本环境块。特别是,您可以使用 Unicode 版本的 CreateProcess 并向其传递 Ansi 环境块,反之亦然。

因此,如果您实际上使用 unicode 环境块,则设置 CREATE_UNICODE_ENVIRONMENT必需的,因为没有其他常规方式(除了一些丑陋的启发式方法)操作系统可能会意识到它是unicode。

现在关于您的问题:

  1. 如果您没有显式传递环境块 - 新创建的进程最初将具有与其创建者相同的环境变量。除非您需要对新创建的进程进行一些额外的配置 - 仅此而已。

  2. 如果将环境块传递给新创建的进程 - 您必须手动构造它或从某个地方获取它。无论哪种方式,您必须知道它是否是unicode。

  3. 新进程的父进程是它的创建者。在您的特定情况下 - 您的流程。

  4. 这仅取决于环境块的创建方式。如果您总是传递通过调用 GetEnvironmentStrings 获得的内容 - 那么它是 unicode 格式的,前提是您使用定义的 UNICODE 进行编译。然后,如果您正在使用 unicode 进行编译,则应该设置 CREATE_UNICODE_ENVIRONMENT 。另一方面,如果您手动构建它 - 即使您不使用 unicode 进行编译,您也可以使用 unicode 构建它。因此 - 您应该根据构建环境块的方式设置 CREATE_UNICODE_ENVIRONMENT,而不是根据编译定义。

  5. 如前所述,CreateProcessACreateProcessW 都可以与 Ansi 或 Unicode 环境块一起使用。这正是需要此标志的原因。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文