如何正确启动进程并转发 stdin/stdout/stderr

How do I correctly launch a process and forward stdin/stdout/stderr?

本文关键字:stdin stdout stderr 转发 何正确 启动 进程      更新时间:2023-10-16

我正在使用CreateProcess来启动交互式脚本解释器,并希望透明地将stdin/stdout/stderr转发到解释器。

我的第一次尝试是设置传递给CreateProcessSTARTUPINFO结构,例如

STARTUPINFOA si = { sizeof( si ) };
si.hStdError = ::GetStdHandle( STD_ERROR_HANDLE );
si.hStdOutput = ::GetStdHandle( STD_OUTPUT_HANDLE );
si.hStdInput = ::GetStdHandle( STD_INPUT_HANDLE );
si.dwFlags |= STARTF_USESTDHANDLES;

即我试图使脚本解释器进程使用与我的启动器进程相同的句柄进行读取/写入。不过这似乎不起作用(我什至不确定这些标准句柄是否可以继承)。

基于使用重定向输入和输出创建子进程示例的第二个想法是设置三个管道以转发写入任何管道的所有数据。由于我不知道如何等待数据写入多个文件(WaitForMultipleObjects无法在管道上同步),因此我正在考虑使用三个线程,每个线程都在管道上执行阻塞ReadFile调用。

我怀疑这可能是矫枉过正,所以我想知道:有没有更简单的方法可以做到这一点?我根本不需要对从/传递到脚本解释器的数据进行任何类型的处理。

作为旁注,在 Linux 上,我使用 execvp 仅将当前进程替换为脚本解释器进程,但在 Windows 上,我需要在主线程处于挂起状态的情况下启动脚本解释器(以便我可以做一些字节码修补) - 所以即使_execvp似乎在 Windows 上可用, 我不得不使用CreateProcess。

为了等待多个文件或管道上的 I/O,您可以在其中每个文件上发出异步 I/O 请求,然后等待所述请求的完成。类似这些内容(未经测试):

HANDLE file1, file2; // initialized somehow
HANDLE events[2];
events[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
events[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
OVERLAPPED overlapped1 = {0};
overlapped1.hEvent = events[0];
OVERLAPPED overlapped2 = {0};
overlapped2.hEvent = events[1];
ReadFile(file1, buffer1, size1, NULL, &overlapped1);
ReadFile(file2, buffer2, size2, NULL, &overlapped2);
WaitForMultipleObjects(2, events, FALSE, INFINITE);

ReadFileWaitForMultipleObjects 需要在循环中调用。检查 WaitForMultipleObjects 的返回值以了解哪个操作已完成,使用 GetOverlappedResult 发现该操作的结果(是否成功,如果是,则检索了多少字节),处理数据,如果要从中读取更多内容,请再次调用该句柄的 ReadFile,然后返回等待。这有点类似于 Linux 中由 select 驱动的非阻塞 I/O 循环。

更高级的技术是 I/O 完成端口。这允许人们有一个线程池处理大量的异步 I/O.通常用于 Web 服务器等,对于您的情况来说可能矫枉过正。

如果您

确保不在 CreateProcessdwFlags参数中传递 CREATE_NO_WINDOW 参数,则按 OP 所示弹出STARTUPINFO可以正常工作。