5

我正在为第三方控制台应用程序编写 GUI,我希望它能够捕获控制台窗口的输出并将其添加到 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);
}

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

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

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

4

1 回答 1

1

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

我只是遇到了同样的问题并在 C++ 中解决了它,但同样的技术应该适用于任何其他编程语言,因为这是一个 WinAPI 特定的问题。我已经为希望使用 CreateProcess 创建 srcds 服务器并在 Windows 上重定向输入和输出的任何人描述了一个解决方案。

这个 github repo 汇总了控制台句柄和标准句柄如何在窗口中协同工作。 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 [输入]

控制台输入缓冲区的句柄。句柄必须具有 GENERIC_READ 访问权限。有关详细信息,请参阅控制台缓冲区安全和访问权限。

但是在这里 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

免费控制台

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

写控制台输入

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

读取控制台输出

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

于 2021-11-20T17:03:02.050 回答