使用重定向的标准输入处理子进程中的 kbhit

Posted

技术标签:

【中文标题】使用重定向的标准输入处理子进程中的 kbhit【英文标题】:Dealing with kbhit in child process with redirected stdin 【发布时间】:2018-09-04 13:41:11 【问题描述】:

我编写了一个程序,它启动另一个进程并使用 Windows API(CreateProcess、CreatePipe 等)将其标准 I/O 重定向到管道

程序应该启动多个不同的控制台程序并使用 stdio 与它们通信。

这一切都运行良好(我可以写入进程的标准输入并使用管道从进程中读取),直到我尝试启动并与使用 kbhit 的程序通信。

为了简化我想启动的程序对标准输入的作用:

while(1)

    if(kbhit())
    
        fgets(line, sizeof(line), stdin);
        //do something with line
    
    Sleep(100);
  

结果是 fgets 永远不会被调用,因为 kbhit 不会返回 true,即使我已写入已将 stdin 重定向到的管道。我知道,因为我已经调试到另一个程序。我试图删除 kbhit 的调用,然后它确实有效,但我无法更改该代码。

有没有办法向进程发送一些东西,以便子进程中的 kbhit 返回 true?

【问题讨论】:

我想kbhit 直接读取键盘而不是通过标准输入读取。为什么在这里需要kbhit?这是XY Problem吗? 我没有制作的另一个程序使用 kbhit,我无法更改它 kbhit() 不是 C++ 中的标准函数。您需要阅读用于构建“其他程序”的编译器/库的文档,以便了解如何与之交互 - 假设这是可能的。 _kbhit 从控制台读取(CONIN$ 设备)。它忽略标准输入。您写入标准输入的内容将被忽略 有没有办法在那里写东西? 【参考方案1】:

_kbhit 函数检查 控制台 是否有最近的击键。它从不检查标准输入,而是打开CONIN$(控制台输入)并始终从这里读取。

所以在这里重定向标准输入什么都没有。如果我们与孩子共享同一个控制台,我们可以使用WriteConsoleInput 来完成此任务。将字符串写入孩子的示例:

void write_to_conin(PCWSTR msg)

    if (ULONG len = (ULONG)wcslen(msg))
    
        if (INPUT_RECORD* lpBuffer = new INPUT_RECORD[len])
        
            INPUT_RECORD* pir = lpBuffer;
            ULONG n = len;
            do 
            
                WCHAR UnicodeChar = *msg++;
                WORD wVirtualKeyCode = UnicodeChar;
                DWORD dwControlKeyState = CAPSLOCK_ON;

                if ((USHORT)(UnicodeChar - 'a') <= (USHORT)('z' - 'a'))
                
                    dwControlKeyState = 0;
                    wVirtualKeyCode &= ~0x20;
                

                pir->Event.KeyEvent.bKeyDown = TRUE;
                pir->Event.KeyEvent.dwControlKeyState = dwControlKeyState;
                pir->Event.KeyEvent.wRepeatCount = 1;
                pir->Event.KeyEvent.uChar.UnicodeChar = UnicodeChar;
                pir->Event.KeyEvent.wVirtualKeyCode = wVirtualKeyCode;
                pir->Event.KeyEvent.wVirtualScanCode = (WORD)MapVirtualKey(wVirtualKeyCode, MAPVK_VK_TO_VSC);
                pir++->EventType = KEY_EVENT;
             while (--n);

            HANDLE hcon = CreateFileW(L"CONIN$", FILE_GENERIC_WRITE, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

            if (hcon != INVALID_HANDLE_VALUE) 
             
                WriteConsoleInput(hcon, lpBuffer, len, &n); 
                CloseHandle(hcon); 
            

            delete [] lpBuffer;
        
    

【讨论】:

你的解释很有道理。我尝试以各种方式使用您的代码,但我只能通过创建一个调用实际进程的包装进程来使其工作。 @JimmyT.,您必须连接到同一个控制台。如果您有一个 GUI 应用程序,您需要调用 AttachConsole(child_pid),或者更简单地说,事先调用 AllocConsole 让孩子继承您的控制台。 使用新的 ConPTY 系统将更容易处理此类问题,该系统将在 Windows 10 的秋季更新中提供。ConPTY 为 conhost.exe 实例提供 I/O 通道。目标受众是替代控制台应用程序,例如 ConEmu,生成和使用 UTF-8 虚拟终端序列。但这对于与控制台应用程序交互的类似 pexpect 的库同样适用。 问题是我需要启动多个进程并与之通信,所以我不能让它们都共享同一个控制台,这就是为什么需要为每个进程实例提供单独控制台的包装进程. @JimmyT。是的,我如何回答 - 如果我们与孩子共享同一个控制台

以上是关于使用重定向的标准输入处理子进程中的 kbhit的主要内容,如果未能解决你的问题,请参考以下文章

重定向子进程标准输出

使用带有标准输入和标准输出重定向的 2 进程管道时如何避免标准输入上的重复输入

C - 将标准输入/输出重定向到单个双向文件描述符

Linux标准输入输出与重定向详解果断收藏

Python子进程将数据定向到标准输入

19重定向管道与popen模型