使用新控制台 window 创建进程,但覆盖一些标准 i/o 句柄

CreateProcess with new console window, but override some std i/o handles

如果使用带有标志 CREATE_NEW_CONSOLE 的 CreateProcess,新进程会将其标准输入、输出和错误句柄定向到新控制台 window。如果要覆盖 I/O 流,可以通过在 STARTUPINFO 字段 hStdOutput、hStdInput 和 hStdError 中设置句柄并设置标志 STARTF_USESTDHANDLES.

来实现。

但是如果您只想覆盖其中一个句柄怎么办?例如,我可能想将 stderr 重定向到一个文件,同时让 stdout 和 stdin 连接到新控制台 window.

STARTF_USESTDHANDLES 标志告诉 CreateProcess 替换所有句柄,而不是将它们连接到新控制台 window 的句柄。所以看来我们必须提供所有三个句柄。显然我可以将 hStdError 设置为日志文件的句柄,但是 hStdInput 和 hStdOutput 应该使用什么值?

我尝试使用 NULL,它似乎适用于 Windows 8.1,但不适用于 Windows 7。

我还考虑过先创建一个控制台 window,然后使用新控制台 window 缓冲区的句柄调用 CreateProcess(并省略 CREATE_NEW_CONSOLE 标志)。不幸的是,父进程也是一个控制台应用程序,控制台应用程序似乎无法创建第二个控制台window。

如果我对 this documentation 的解释正确,您应该对其他两个句柄使用 GetStdHandle(STD_INPUT_HANDLE)GetStdHandle(STD_OUTPUT_HANDLE)

根据这篇 MSDN 支持文章:

How to spawn console processes with redirected standard handles

If the parent process only wishes to redirect one or two standard handles, specifying GetStdHandle() for the specific handles causes the child to create the standard handle as it normally would without redirection. For example, if the parent process only needs to redirect the standard output and error of the child process, then the hStdInput member of the STARTUPINFO structure is filled as follows:

hStdInput = GetStdHandle(STD_INPUT_HANDLE);

根据 GetStdHandle() documentation:

STD_INPUT_HANDLE
(DWORD)-10
The standard input device. Initially, this is the console input buffer, CONIN$.

STD_OUTPUT_HANDLE
(DWORD)-11
The standard output device. Initially, this is the active console screen buffer, CONOUT$.

STD_ERROR_HANDLE
(DWORD)-12
The standard error device. Initially, this is the active console screen buffer, CONOUT$.

...

The standard handles of a process may be redirected by a call to SetStdHandle, in which case GetStdHandle returns the redirected handle. If the standard handles have been redirected, you can specify the CONIN$ value in a call to the CreateFile function to get a handle to a console's input buffer. Similarly, you can specify the CONOUT$ value to get a handle to a console's active screen buffer.

Attach/detach behavior

When attaching to a new console, standard handles are always replaced with console handles unless STARTF_USESTDHANDLES was specified during process creation.

If the existing value of the standard handle is NULL, or the existing value of the standard handle looks like a console pseudohandle, the handle is replaced with a console handle.

When a parent uses both CREATE_NEW_CONSOLE and STARTF_USESTDHANDLES to create a console process, standard handles will not be replaced unless the existing value of the standard handle is NULL or a console pseudohandle.

因此,如果 parent 进程的 STDIN 未被重定向,GetStdHandle(STD_INPUT_HANDLE) 将 return 为 NULL 或引用 CONIN$ 的 pseudo-handle。当该值通过 STARTUPINFO 传递给 child 进程时,child 进程将收到一个控制台句柄,用于它恰好在 运行 中的任何控制台的 STDIN。另一方面,如果 parent 进程的 STDIN 已被重定向,GetStdHandle(STD_INPUT_HANDLE) 将 return 一个真正的 file/pipe/etc 的实际句柄,child 将继承并且访问。

这同样适用于 STDOUT 和 STDERR。

因此,如果您想重定向 child 的 STDIN/OUT/ERR 句柄,则必须将 hStdInput/Output/Error 设置为您自己的句柄。如果您希望 child 接收默认句柄,请使用 GetStdHandle() 并让 CreateProcess() 根据 parent 是否本身来决定 child 接收哪种句柄是否被重定向。

在 Windows 7 上,至少 documentation here(如现有答案所引用)具有误导性。推荐的方法仅在 parent 进程没有重定向输入时才有效。

这是在我的机器上观察到的实际行为:

  • 当进程打开 CONOUT$ 的句柄时(例如)返回的句柄总是具有相同的数值(在我的机器上,CONOUT$ 的句柄总是 7)除非该句柄已经存在。

  • 所以如果你是一个控制台进程,并且你没有使用重定向输出启动,你的标准输出句柄是 7。如果你打开另一个 CONOUT$ 句柄,它将有不同的值。如果您关闭句柄 7,然后打开一个指向 CONOUT$ 的句柄,您将再次得到 7。

  • 启动控制台进程时,child 进程中通常会出现一个 CONOUT$ 的句柄,其神奇值为 7(无论重定向如何)。如果您使用了 STARTF_USESTDHANDLES,那么当且仅当您将 7 指定为标准输出句柄时,child 的标准输出才会转到控制台。如果您指定具有任何其他值的 CONOUT$ 句柄,它将不起作用。

  • 有时,当控制台进程启动并重定向标准输出时,child 进程中不存在 CONOUT$ 的句柄。特别是,当 cmd.exe 启动控制台进程并重定向标准输出时,就会发生这种情况。到目前为止,我一直无法弄清楚要将哪些参数组合与 CreateProcess 结合使用才能实现这一目标。

  • CONIN$ 的行为类似;在我的机器上,CONIN$ 的神奇值是 3。(我还没有研究标准错误是如何工作的。)

结果是,我认为除非您完全控制 parent 进程的启动方式,否则使用此行为是不安全的,因为除非标准句柄已经指向控制台没有可靠的方法来获取具有正确魔法值的控制台句柄。

相反,使用 CREATE_NEW_CONSOLE 不使用 STARTF_USESTDHANDLES 启动代理进程。从 that 过程中,因为您知道它是如何启动的,所以您知道标准句柄将具有正确的魔法值,因此将它们指定为 [=50= 的句柄是安全的] 过程。