CreateProcess 带有新的控制台窗口,但会覆盖一些 std i/o 句柄

2022-01-11 00:00:00 console winapi c++ stdio createprocess

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

If you use CreateProcess with the flag CREATE_NEW_CONSOLE, the new process has its standard input, output, and error handles directed to the new console window. If you want to override the I/O streams, you can do so by setting the handles in STARTUPINFO fields hStdOutput, hStdInput, and hStdError and setting the flag STARTF_USESTDHANDLES.

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

But what if you want to override only one of the handles? For example, I might want to redirect stderr to a file while leaving the stdout and stdin connected to the new console window.

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

The STARTF_USESTDHANDLES flag tells CreateProcess to replace all of the handles instead of connecting them to the ones for the new console window. So it seems we must provide all three handles. Obviously I can set hStdError to the handle of the log file, but what values should be used for hStdInput and hStdOutput?

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

I tried using NULL, which seems to work on Windows 8.1, but it doesn't work on Windows 7.

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

I also thought about creating a console window first, and then calling CreateProcess with handles to the new console window's buffers (and omitting the CREATE_NEW_CONSOLE flag). Unfortunately, the parent process is also a console application, and it seems a console application cannot create a second console window.

推荐答案

根据这篇 MSDN 支持文章:

According to this MSDN Support article:

如何使用重定向的标准句柄生成控制台进程

如果父进程只希望重定向一个或两个标准句柄,为特定句柄指定 GetStdHandle() 会导致子进程创建标准句柄,因为它通常不会重定向. 例如,如果父进程只需要重定向子进程的标准输出和错误,那么STARTUPINFO结构的hStdInput成员填充如下:

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() 文档:

According to the GetStdHandle() documentation:

STD_INPUT_HANDLE
(DWORD)-10
标准输入设备.最初,这是控制台输入缓冲区,CONIN$.

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

STD_OUTPUT_HANDLE
(双字)-11
标准输出设备.最初,这是活动控制台屏幕缓冲区 CONOUT$.

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

STD_ERROR_HANDLE
(双字)-12
标准错误设备.最初,这是活动控制台屏幕缓冲区 CONOUT$.

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

...

可以通过调用 SetStdHandle 来重定向进程的标准句柄,在这种情况下 GetStdHandle 返回重定向的句柄.如果标准句柄已被重定向,您可以在调用 CreateFile 函数时指定 CONIN$ 值以获取控制台输入缓冲区的句柄.同样,您可以指定 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.

附加/分离行为

连接到新控制台时,标准句柄总是被控制台句柄替换,除非在进程创建期间指定了 STARTF_USESTDHANDLES.

如果标准句柄的现有值为 NULL,或者标准句柄的现有值看起来像控制台伪句柄,则将句柄替换为控制台句柄.

当父进程同时使用 CREATE_NEW_CONSOLE 和 STARTF_USESTDHANDLES 创建控制台进程时,标准句柄不会被替换,除非标准句柄的现有值为 NULL 或控制台伪句柄.

因此,如果父进程的 STDIN 未被重定向,GetStdHandle(STD_INPUT_HANDLE) 将返回 NULL 或引用 CONIN$ 的伪句柄.当该值通过 STARTUPINFO 传递给子进程时,子进程将接收到它恰好在其中运行的任何控制台的 STDIN 的控制台句柄.另一方面,如果父进程的 STDIN已被重定向,GetStdHandle(STD_INPUT_HANDLE) 将返回一个实际文件/管道/等的实际句柄,子进程将继承和访问.

So, if the parent process's STDIN has NOT been redirected, GetStdHandle(STD_INPUT_HANDLE) will return either NULL or a pseudo-handle that refers to CONIN$. When that value is passed to the child process via STARTUPINFO, the child process will receive a console handle for the STDIN of whichever console it happens to be running in. On the other hand, if the parent process's STDIN has been redirected, GetStdHandle(STD_INPUT_HANDLE) will return an actual handle to a real file/pipe/etc, which the child will inherit and access.

这同样适用于 STDOUT 和 STDERR.

The same applies to STDOUT and STDERR.

所以,如果你想重定向孩子的 STDIN/OUT/ERR 句柄,你必须将 hStdInput/Output/Error 设置为你自己的句柄.如果您希望子进程接收默认句柄,请使用 GetStdHandle() 并让 CreateProcess() 根据父进程本身是否被重定向来决定子进程接收的句柄类型与否.

So, if you want to redirect the child's STDIN/OUT/ERR handles, you have to set hStdInput/Output/Error to your own handles. If you want the child to receive default handles, use GetStdHandle() and let CreateProcess() decide what kind of handles the child receives based on whether the parent is itself being redirected or not.

相关文章