MSDN是错误的并夸大了标志的意义,还是这是一个真正的要求?lpEnvironment [in, optional]
[...] 如果由lpEnvironment指向的环境块包含Unicode字符,请确保dwCreationFlags包括CREATE_UNICODE_ENVIRONMENT。 如果此参数为NULL且父进程的环境块包含Unicode字符,则还必须确保dwCreationFlags包括CREATE_UNICODE_ENVIRONMENT。
我看过从未设置标志的代码,它似乎可以工作,但我有点偏执地想要完全遵守MSDN的规定。话虽如此,我不确定你是否真的可以在不走极端的情况下遵循MSDN的规则。
当lpEnvironment为NULL时必须设置(或不设置)CREATE_UNICODE_ENVIRONMENT,这让我感到荒谬。
如果我不传递环境块,那么CreateProcess必须自己获取该块。在这种情况下,它比我更能知道块的类型。
我怎么知道该块实际上包含Unicode字符?
我需要获取该块并检查其中是否有当前代码页之外的字符吗?(我认为这就是MSDN在这里所说的“Unicode字符”)
如果我确实需要获取env-block,那么我可能会将其作为lpEnvironment传递而不是NULL,那么为什么要允许NULL?
每个调用CreateProcess的调用者都必须获取并检查env-block似乎是一个疯狂的要求;这肯定是API本身应该处理的事情。
当它说“父进程”时,它是指即将成为新父级的我的进程,还是指我的进程的父级?我最初阅读MSDN时认为我必须以某种方式告诉CreateProcess调用是否已传递了ANSI或Unicode环境块,但这显然不是这种情况。
我假设,在基于NT的操作系统上,所有进程都具有Unicode env块,如果需要,在进程创建时从ANSI转换,并且进程不会保留传递给CreateProcess的任何数据块。
(也许整个问题都是Win9x时代的遗留问题,当时操作系统本身不是Unicode?即使那样,我也看不出应用程序代码如何比操作系统本身更能做出决策,也看不出为什么应该期望这样做。)
除了从未设置标志的代码外,我还看到了始终在编译时定义UNICODE的代码。当要求是在运行时检查env块中的内容,并且代码可能位于加载到外部进程的DLL中时,这毫无意义。
env块是进程范围的,因此在编译时定义UNICODE似乎不相关。
如果只是调用CreateProcessA或CreateProcessW的问题,则当块为NULL时,标志应该是隐式的,因此这也没有意义。
不过,我所做的肯定是多余的。CreateProcess不可能那么愚蠢,对吧?
另一方面,我们正在谈论的是CreateProcess。它并不是最好设计的API,还有许多其他陷阱(就我而言):
- 如果参数字符串是const,则失败,因为它会在原地修改它。
- 使第一个参数可选,从而邀请人们忘记在第二个参数中引用exe路径。
- 要求在第二个参数中正确引用带引号的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 时起作用?如果有,在这种情况下,应用程序如何比操作系统本身更好地知道标志的正确值呢?