17

CreateProcess文档状态(我的大胆强调):

lpEnvironment [输入,可选]

[...] 如果 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 字符”的意思。)

    如果我必须获得 env 块,那么我不妨将它传递给 lpEnvionment 而不是 NULL,那么为什么还要允许 NULL?

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

  3. 当它说“父进程”时,它甚至是指我的进程,即将成为新的父进程,还是意味着我的进程的父进程?我对 MSDN 的初步阅读让我觉得我必须以某种方式判断启动我的进程的 CreateProcess 调用是否已通过 ANSI 或 Unicode 环境块,但事实并非如此。

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

    (也许这整个事情是操作系统本身不是 Unicode 的 Win9x 时代的遗留物?即便如此,我也看不出应用程序代码如何比操作系统本身做出更好的决定,也不知道为什么它应该预计会。)

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

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

显然,这并不能断定该标志在所有未来的操作系统中总是无关紧要的,但我真的看不出它会如何。

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

  • 如果操作系统总是将 Parent 的 env-block 存储为 Unicode——如果 Grandparent 传递了一个 ANSI 块,在 Parent 的创建过程中将它从 ANSI 转换——那么当 Parent 传递一个 NULL 块时 CreateProcess 将错误地注意标志. CreateProcess 必须知道 Child 将继承的块总是 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 的情况)。

任何人都可以想到当 env-block 为 NULL 时标志可能很重要的任何场景?在那种情况下,应用程序怎么可能比操作系统本身更了解标志的正确值?

4

1 回答 1

8

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

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

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

现在关于您的问题:

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

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

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

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

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

于 2010-11-17T17:14:59.657 回答