如何在不重定向标准输出的情况下读取进程的控制台输出?

How to read console output of a process without redirecting standard output?

我正在为第三方控制台应用程序编写 GUI,我希望它捕获控制台的输出 window 并将其添加到 GUI 中的文本框中。这看起来很简单,我要做的就是重定向目标进程的输出流。

但是,当我这样做时,控制台应用程序抛出错误:

CTextConsoleWin32::GetLine: !GetNumberOfConsoleInputEvents

导致此错误的当前代码是这样的:

// This gets called once after the application has initialized.
private void StartServer()
{
    ProcessStartInfo processStartInfo = new ProcessStartInfo();
    processStartInfo.FileName = srcdsExeFile;
    processStartInfo.UseShellExecute = false;
    processStartInfo.CreateNoWindow = true;
    processStartInfo.RedirectStandardOutput = true;
    processStartInfo.RedirectStandardError = true;
    processStartInfo.RedirectStandardInput = true;

    serverProcess = Process.Start(processStartInfo);
    serverProcess.EnableRaisingEvents = true;
    serverProcess.Exited += new EventHandler(Server_Exited);
    serverProcess.OutputDataReceived += ServerProcess_OutputDataReceived;
    serverProcess.ErrorDataReceived += ServerProcess_ErrorDataReceived;
    serverProcess.BeginOutputReadLine();
    serverProcess.BeginErrorReadLine();
}

// This is (like seen above) an event handler for serverProcess.ErrorDataReceived.
private void ServerProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.Output.WriteLine("\n\nServer Error: " + e.Data + "\n\n");
}

// This is (like seen above) an event handler for serverProcess.OutputDataReceived.
private void ServerProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    Console.Output.WriteLine(e.Data);
}

当外部应用程序进行初始化时,上面的代码确实可以工作一分钟左右,但在初始化过程中的特定点后崩溃。

经过一些研究后发现,第三方控制台应用程序依赖于输出流作为控制台,这就是为什么当我尝试重定向它时它会崩溃。尝试在不重定向的情况下访问输出流也会导致错误提示我必须先重定向它。

这让我想到了我的实际问题:
是否可以在不重定向输出流的情况下读取控制台应用程序的输出?

所以这个问题在过去几年里被问过好几次了。

我只是 运行 遇到了同样的问题并在 C++ 中解决了它,但同样的技术应该适用于任何其他编程语言,因为这是一个特定于 WinAPI 的问题。 我已经为希望使用 CreateProcess 创建 srcds 服务器并在 windows.

上重定向输入和输出的任何人描述了一个解决方案

此 github 存储库汇总了控制台句柄和标准句柄如何在 windows 中协同工作。 https://github.com/rprichard/win32-console-docs

还有微软文档 https://docs.microsoft.com/en-us/windows/console/creation-of-a-console

我强烈建议阅读这篇文章,因为这很清楚为什么 srcds 在重定向标准输入时失败。

问题

a) Windows 控制台句柄不等于标准输入和输出句柄。

b) 在 windows 中无法重定向控制台句柄。

c) GetNumberOfConsoleInputEvents 需要一个有效的控制台句柄,其输入句柄不是文件、管道。它必须是一个 ConsoleHandle!

因为没有人真正了解 GetNumberOfConsoleInputEvents 失败的原因,而阅读文档后应该很明显。

https://docs.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents

它明确指出:

hConsoleInput [in]

A handle to the console input buffer. The handle must have the GENERIC_READ access right. For more information, see Console Buffer Security and Access Rights.

但是这里 https://github.com/rprichard/win32-console-docs#allocconsole-attachconsole-modern 据解释,当您重定向管道时,它几乎会破坏控制台输入缓冲区。所以你必须实际使用控制台输入缓冲区而不是 StdHandles。

解决方案

幸运的是 WinAPI 为我们提供了几个选项来访问现有进程的标准句柄。这只是非常棘手而且没有很好的记录! 您可以附加到控制台并获取 STDHandles。复制它们并做任何你喜欢的事情。 请注意,AttachConsole(ProcessId) 要求您当前进程没有附加控制台,因此您必须调用 FreeConsole();

下面是一段代码,说明如何使用 WinAPI 将单个字母发送到另一个应用程序的控制台。您还可以随时使用 GetStdHandle 获取控制台句柄并写入它。

        int ProcessId = GetProcessId(ProcessInfo.hProcess);
        if (ProcessId <= 0)
        {
            printf("Process terminated.\n");
            break;
        }
        printf("Process Id: %d\n", ProcessId);

        FreeConsole();
        if (!AttachConsole(ProcessId))
        {
            printf("Attach failed with error: %d\n", GetLastError());
            exit(1);
        }

        INPUT_RECORD ir[2];
        ir[0].EventType = KEY_EVENT;
        ir[0].Event.KeyEvent.bKeyDown = TRUE;
        ir[0].Event.KeyEvent.dwControlKeyState = 0;
        ir[0].Event.KeyEvent.uChar.UnicodeChar = 'u';
        ir[0].Event.KeyEvent.wRepeatCount = 1;
        ir[0].Event.KeyEvent.wVirtualKeyCode = 'U';
        ir[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey('U', MAPVK_VK_TO_VSC);

        ir[1].EventType = KEY_EVENT;
        ir[1].Event.KeyEvent.bKeyDown = FALSE;
        ir[1].Event.KeyEvent.dwControlKeyState = 0;
        ir[1].Event.KeyEvent.uChar.UnicodeChar = 'u';
        ir[1].Event.KeyEvent.wRepeatCount = 1;
        ir[1].Event.KeyEvent.wVirtualKeyCode = 'U';
        ir[1].Event.KeyEvent.wVirtualScanCode = MapVirtualKey('U', MAPVK_VK_TO_VSC);

        DWORD dwTmp = 0;
        WriteConsoleInputA(GetStdHandle(STD_INPUT_HANDLE), ir, 2, &dwTmp);

        FreeConsole();
        if (!AttachConsole(ATTACH_PARENT_PROCESS))
        {
            printf("Attach failed with error: %d\n", GetLastError());
            exit(1);
        }

所以解决方案就是通过附加到 SRCDS 进程的控制台来写入控制台输入缓冲区。 只需调用 AttachConsoleFreeConsole。和 WriteConsoleInput.

要阅读输出,您只需调用 ReadConsoleOutput

如需进一步阅读,请访问文档:

GetNumberOfConsoleInputEvents

https://docs.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents

附加控制台

https://docs.microsoft.com/en-us/windows/console/attachconsole

FreeConsole

https://docs.microsoft.com/en-us/windows/console/freeconsole

WriteConsoleInput

https://docs.microsoft.com/en-us/windows/console/writeconsoleinput

ReadConsoleOutput

https://docs.microsoft.com/en-us/windows/console/readconsoleoutput