Win32的CreateProcess:何时*真正*需要CREATE_UNICODE_ENVIRONMENT?

18
CreateProcess文档中指出(我加粗了强调):

lpEnvironment [in, optional]

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

MSDN是错误的并夸大了标志的意义,还是这是一个真正的要求?
我看过从未设置标志的代码,它似乎可以工作,但我有点偏执地想要完全遵守MSDN的规定。话虽如此,我不确定你是否真的可以在不走极端的情况下遵循MSDN的规则。
当lpEnvironment为NULL时必须设置(或不设置)CREATE_UNICODE_ENVIRONMENT,这让我感到荒谬。
  1. 如果我不传递环境块,那么CreateProcess必须自己获取该块。在这种情况下,它比我更能知道块的类型。

  2. 我怎么知道该块实际上包含Unicode字符?

    我需要获取该块并检查其中是否有当前代码页之外的字符吗?(我认为这就是MSDN在这里所说的“Unicode字符”)

    如果我确实需要获取env-block,那么我可能会将其作为lpEnvironment传递而不是NULL,那么为什么要允许NULL?

    每个调用CreateProcess的调用者都必须获取并检查env-block似乎是一个疯狂的要求;这肯定是API本身应该处理的事情。

  3. 当它说“父进程”时,它是指即将成为新父级的我的进程,还是指我的进程的父级?我最初阅读MSDN时认为我必须以某种方式告诉CreateProcess调用是否已传递了ANSI或Unicode环境块,但这显然不是这种情况。

    我假设,在基于NT的操作系统上,所有进程都具有Unicode env块,如果需要,在进程创建时从ANSI转换,并且进程不会保留传递给CreateProcess的任何数据块。

    (也许整个问题都是Win9x时代的遗留问题,当时操作系统本身不是Unicode?即使那样,我也看不出应用程序代码如何比操作系统本身更能做出决策,也看不出为什么应该期望这样做。)

  4. 除了从未设置标志的代码外,我还看到了始终在编译时定义UNICODE的代码。当要求是在运行时检查env块中的内容,并且代码可能位于加载到外部进程的DLL中时,这毫无意义。

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

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

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

不过,我所做的肯定是多余的。CreateProcess不可能那么愚蠢,对吧?

另一方面,我们正在谈论的是CreateProcess。它并不是最好设计的API,还有许多其他陷阱(就我而言):

  1. 如果参数字符串是const,则失败,因为它会在原地修改它。
  2. 使第一个参数可选,从而邀请人们忘记在第二个参数中引用exe路径。
  3. 要求在第二个参数中正确引用带引号的exe路径,即使在第一个参数中明确给出。

因此,也许假设它行为智能或者可能为调用者处理杂务是不正确的...

我不知道是否应该从自己的代码中删除多余的东西,还是将其添加到所有其他代码中。哎呀。:-)

添加于2010年11月18日:

在Windows 2000到Windows 7中,当env-block为NULL时,该标志似乎是无关紧要的。请参见下面的测试结果。

显然,这并不能证明在所有未来的操作系统中该标志都将无关紧要,但我真的看不出它可能是其他情况。

假设我们有创建Parent的Grandparent,而Parent即将创建Child:

  • 如果操作系统始终将父进程的环境块存储为Unicode格式--在父进程创建时,如果祖父进程传递了一个ANSI块,则已将其从ANSI转换为Unicode--那么当父进程传递一个空块时,CreateProcess将错误地注意到标志。CreateProcess必须知道Child将继承的块将始终是Unicode。

  • 或者,操作系统可能会将父进程的环境块完全存储为来自祖父进程的块。(这似乎不太可能,但有可能。)在这种情况下,父进程无法检测到祖父进程传递的块的类型。同样,CreateProcess必须知道块的类型并忽略标志。

这是我今天早上编写的一个测试,以不同的方式启动子进程,并使子进程报告一个环境变量(仅出于简洁起见的“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 情况有关)。
是否有任何情况下该标志可能在 env-block 为 NULL 时起作用?如果有,在这种情况下,应用程序如何比操作系统本身更好地知道标志的正确值呢?

2
已提交一个拉取请求以更正进程创建 API 文档:https://github.com/MicrosoftDocs/sdk-api/pull/1392 - Tom Honermann
更改它的PR已于2023年1月20日合并,感谢@TomHonermann。 - Peter Hull
1个回答

9
请注意,在CreateProcess函数的声明中,lpEnvironment参数被声明为LPVOID。这是什么意思?这意味着您可以使用Ansi / Unicode版本的CreateProcess函数,并以任何组合方式传递Ansi / Unicode版本的环境块。特别是,您可以使用Unicode版本的CreateProcess并传递Ansi环境块,反之亦然。
因此,只有在实际使用Unicode环境块时才需要设置CREATE_UNICODE_ENVIRONMENT,因为除了一些丑陋的启发式方法外,操作系统没有其他“常规”方法可以认识到它是Unicode。
现在关于您的问题:
1.如果您不明确传递环境块,则新创建的进程最初将具有与其创建者相同的环境变量。除非您需要对新创建的进程进行一些额外配置-否则不需要更多操作。
2.如果您将环境块传递给新创建的进程,则必须手动构建它或从某个地方获取它。无论哪种方式,您都必须知道它是否是Unicode。
3.新进程的父进程是其创建者。在您的情况下,就是您的进程。
4.这完全取决于如何创建环境块。如果您始终传递通过调用GetEnvironmentStrings获得的内容,则它将在定义了UNICODE的情况下为Unicode。然后,如果您正在使用Unicode进行编译,则应根据是否使用Unicode来设置CREATE_UNICODE_ENVIRONMENT。另一方面,如果您手动构造它,则即使不使用Unicode编译,也可以以Unicode方式构造它。因此,应根据如何构造环境块而不是根据编译定义来设置CREATE_UNICODE_ENVIRONMENT
5.正如已经提到的,CreateProcessACreateProcessW都可以使用Ansi或Unicode环境块。这正是为什么需要此标志的原因

1
如果lpEnvironment不为NULL,显然需要该标志,否则API就不知道块中有什么。但是,如果lpEnvironment为NULL,何时以及为什么需要该标志呢? - Leo Davidson
2
当lpEnvironemnt为NULL时,不需要使用标志。 - John
7
有点晚了来回复,但这不是 MSDN 所说的。MSDN 明确地表示了某些内容,但可能很难实际遵守(或理解):“如果此参数为 NULL,并且父进程的环境块包含 Unicode 字符,则还必须确保 dwCreationFlags 包括 CREATE_UNICODE_ENVIRONMENT。”这仍然是 7 年后的情况。这个语句以及如何遵守它,就是问题所在。 - Leo Davidson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接