【问题标题】:How to read console output of a process without redirecting standard output?如何在不重定向标准输出的情况下读取进程的控制台输出?
【发布时间】:2018-06-27 15:50:08
【问题描述】:

我正在为第三方控制台应用程序编写 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);
}

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

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

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

【问题讨论】:

  • 当然,您可以反过来做,使用管道将应用程序的输出作为您的输入。
  • 这将如何工作?难道它还不需要重定向应用程序的输出吗?
  • @alexkarlin 我有这个确切的问题。你找到解决方案了吗?
  • @alexkarlin 另外,只是好奇,您是否还为 csgo 服务器制作了 GUI? :)
  • @Jesbus 我没有找到任何解决方案。 :( 我最终将命令行窗口重新定位到 GUI 的中心,但这根本不是一个非常优雅的解决方案。是的,几乎。我正在为 Garry 的 Mod 服务器制作 GUI。:)跨度>

标签: c# windows output-redirect console-output


【解决方案1】:

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

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

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

还有 microsoft 文档 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 为我们提供了几个选项来访问现有进程的 std 句柄。这非常棘手,而且没有很好的记录! 您可以附加到控制台并获取 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

WriteConsoleInput

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

ReadConsoleOutput

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-14
    • 1970-01-01
    • 1970-01-01
    • 2019-03-15
    • 2015-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多