【问题标题】:Win32 CreateProcess: When is CREATE_UNICODE_ENVIRONMENT *really* needed?Win32 CreateProcess:CREATE_UNICODE_ENVIRONMENT *真的*需要什么时候?
【发布时间】:2010-11-17 15:48:18
【问题描述】:

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-block,那么我不妨将它传递给 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 块存储为 Unicode——如果祖父母传递了一个 ANSI 块,则在 Parent 的创建过程中将其从 ANSI 转换——然后 CreateProcess 将错误地注意标志当 Parent 传递一个 NULL 块时。 CreateProcess 必须知道 Child 将继承的块总是 Unicode。

  • 或者,操作系统可能会完全按照来自祖父母的方式存储父级的 env 块。 (这似乎不太可能,但有可能。)在这种情况下,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 时标志可能很重要的任何场景?在那种情况下,应用程序怎么可能比操作系统本身更了解标志的正确值?

【问题讨论】:

    标签: windows winapi unicode createprocess


    【解决方案1】:

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

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

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

    现在关于您的问题:

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

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

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

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

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

    【讨论】:

    • 显然,如果 lpEnvironment 为非 NULL,则需要该标志,否则 API 将不知道块中的内容。但是,如果 lpEnvironment 为 NULL,何时以及为什么需要该标志?
    • lpEnvironemnt 为 NULL 时不需要该标志。
    • 回复有点晚,但这不是 MSDN 所说的。 MSDN 说的很明确,但实际上不可能遵守(或理解):“如果此参数为 NULL 并且父进程的环境块包含 Unicode 字符,则还必须确保 dwCreationFlags 包含 CREATE_UNICODE_ENVIRONMENT。” 7年后仍然这么说。该声明以及如何遵守它就是问题所在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-27
    • 2010-12-13
    • 1970-01-01
    相关资源
    最近更新 更多