【问题标题】:What is a reliable way to send control characters to the STDIN of a child process?将控制字符发送到子进程的 STDIN 的可靠方法是什么?
【发布时间】:2021-10-12 22:16:52
【问题描述】:

背景
我正在构建一个基本的 MFC 应用程序,它可以帮助我运行测试。 GUI 非常简洁。一个按钮开始测试,另一个按钮停止测试,加上一个静态文本对象显示几个字母和数字,指示测试正在执行的阶段。几周以来,这一直令人满意。
如果我的应用程序在执行测试时还进行了视频屏幕录制,我会发现它很有用。大计划是将 FFmpeg 库函数调用编译到代码中以实现此功能,但这肯定需要我数周的时间来学习、尝试和完成。同时,一个快速但足够的解决方案是从下载的 Windows 二进制构建中调用预编译的 ffmpeg.exe
我在::OnBnClickedButtonStarttest() 方法中添加了代码以通过CreateProcess() 调用ffmpeg.exe。这工作正常。 FFmpeg 屏幕录制过程在一个新的控制台窗口中开始,并且很好地完成了它的预期工作。当我选择该控制台窗口并按 Ctrl+C 时,录制将停止并且我拥有所需的视频文件。我还在::OnBnClickedButtonStoptest() 方法中添加了代码,以将所需的Ctrl+C 发送到ffmpeg.exe 进程的STDIN,并在单击STOP 按钮时完成录制。这也工作正常。 大部分时间
我的源代码的这两部分基于 Microsoft 在https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output 上发布的示例。

问题
大部分时间停止屏幕录制过程意味着它不会总是停止。有时解决方案不起作用。而且我找不到任何原因为什么大多数时间都有效的相同解决方案在其他时间碰巧失败。最终我宁愿隐藏 FFmpeg 控制台窗口并让它在幕后工作,但这需要一种可靠的方法来停止子进程,而我当前的代码被证明不能可靠地工作。注意:源代码顶部有一个#define BUFSIZE 128 行。

DWORD dwNumOfBytesToWrite = 7;
DWORD dwWritten = 0;
CHAR chBuf[BUFSIZE] = { 0x03, 0x03, 0x03, 0x03, 0x03, 0x0A, 0x0D, 0x03, 0x0A, 0x0D };
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf, dwNumOfBytesToWrite, &dwWritten, NULL);

虽然发送单个 Ctrl+C (0x03) 应该会停止 ffmpeg 进程,但我还是发送了更多输入以确保。尽管如此,出于某种原因,我必须两次发送上述WriteFile 指令,才能真正看到进程停止。我不知道为什么。不过,这对我来说并不重要,只要第二次确实有效。
让我担心的是,有时子进程不会停止。无论我发送多少次以上WriteFile 行。 我发现了另一个有希望的例子。这次没有匿名管道 - 我怀疑这是不可靠行为的根源。
https://www.techpowerup.com/forums/threads/c-c-c-console-redirection-with-sockets-win32.62350/
这似乎使用自己的命名通道来访问子进程的标准输入。我更喜欢这种方法。不幸的是,它不仅不起作用,而且也无法编译。 Socket thisSocket; 行指的是不存在的类型。没有像Socket 这样的类型,没有大写的S 和小写的ocket。我尝试在其位置使用所有大写的SOCKET 类型,它确实可以编译,但相应修改的WriteFile((HANDLE)thisSocket, chBuf, dwNumOfBytesToWrite, &dwWritten, NULL); 行总是返回FAILURE,并且不会停止子进程。 为了让我的调查更加困难,大量的互联网搜索结果将我带到了与 WINSOCK 相关的主题。

我错过了什么吗?
为了巧妙地要求它完成录制,向 ffmpeg.exe 进程发送 Ctrl+C 的可靠方法是什么?
请注意,我可以通过TerminateProcess(g_ffmpeg_process_handle, 0) 残酷地杀死 FFmpeg 控制台窗口,但这并不总是允许孩子正确关闭视频文件,从而导致屏幕录制损坏。

【问题讨论】:

  • 您是否尝试关闭输入管道以发出 EOF 条件的信号?类似this
  • 不要使用WriteFile() 将字节0x03 发送到衍生进程的STDIN,而是尝试使用GenerateConsoleCtrlEvent() 将CTRL-C 信号发送到衍生进程的控制台。见Can I send a ctrl-C (SIGINT) to an application on Windows?
  • @rustyx:是的,我尝试关闭所有 I/O 句柄以及进程和线程句柄。这没什么区别。当 FFmpeg 进程确实停止时,即使这些句柄仍处于打开状态,它也会停止(并且我在进程停止后关闭它们)。并且当 FFmpeg 进程没有停止时,关闭这些句柄不会触发子进程的停止。我尝试放置第三个按钮,将“发送 Ctrl+C”和“关闭句柄”代码部分分开。
  • @RemyLebeau:在尝试 WriteFile(0x03) 之前,我也尝试过。不幸的是 GenerateConsoleCtrlEvent() 从未关闭子进程。
  • Windows.Graphics.Capture 不是 UWP API。事实上,甚至没有 UWP API 这样的东西。 UWP 是一个平台,它限制了针对该平台的程序的可用 API 表面。 API 本身作为 Windows 运行时类型公开,其中大部分可用于经典桌面应用程序。将系统服务用作备份计划的评级听起来像是优先级倒置问题。

标签: c++ sockets redirect mfc


【解决方案1】:

没有合适的解决方案,但我有一个可以接受的解决方法。

我引入了一个初始值为零的全局变量ffmpChildProc_statusFlag
CreateProcess() 在其隐藏的控制台窗口中成功启动 FFmpeg 子进程时,我将 ffmpChildProc_statusFlag 的值分配为 ONE。
当 FFmpeg 子进程实际终止时,我使用RegisterWaitForSingleObject() 自动启动 CALLBACK 函数。

VOID CALLBACK onSubprocessExit(_In_ PVOID lpParameter, _In_ BOOLEAN TimerOrWaitFired) {
    //// Do clean-up by unregistering this callback instance.
    //// Update _ffmpChildProc_statusFlag_ to have a value of TWO.
}

这些让我可以自信地知道我的努力效果如何。

我还编写了一个EnumWindowsProc 回调,它使用GetWindowThreadProcessId() 返回属于当前检查窗口的进程ID,直到遇到进程ID我的 FFmpeg 子进程,CreateProcess() 在其 PROCESS_INFORMATION structure 参数的 dwProcessId 成员中返回。
我找不到更好的方法来从其已知进程 ID 中获取创建的子进程的窗口句柄。这就是 EnumWindowsUntilProcessHandleFound()

那么这就是我所做的

当完成 FFmpeg 子进程的时间到了,我首先尝试将 WriteFile(0x03) 写入问题中显示的重定向 StdIN 管道 方法。我这样做了 1-5 次,每次检查 ffmpChildProc_statusFlag 的值以查看是否需要再尝试一次,每次尝试后都有几百毫​​秒的暂停。不关闭管道的 Write 端,以便下一次写入尝试成功。
如果子进程还没有结束,那么我关闭3个句柄,对于

  • 管道的写入端,
  • 子进程,
  • 子进程的线程,

然后再次暂停几百毫秒,以便有时间完成回调(如果子进程完成)。
作为最后的尝试,如果 FFmpeg 进程仍在运行,那么我取消隐藏子进程的隐藏控制台窗口,使用户可以使用实际的 Ctrl+ 手动停止它C。但是在最后一次尝试中,我尝试了几次使用SendInput() 以编程方式输入 Ctrl+C。

CWnd* wFFmpPtr = CWnd::FromHandle(ffmpExe_window_handle);
if (!wFFmpPtr->IsWindowVisible()) {
    TRACE(L"\tREVEALING FFmp window.\n");
    wFFmpPtr->ShowWindow(SW_SHOWNORMAL);
} //// if not already visible
SetActiveWindow(ffmpExe_window_handle);
TRACE(L"\tSending Ctrl+C keyboard action to the frontmost window.\n");
INPUT vips;   //// My temporary virtual-input structure to define a keyboard key action.
vips.type = INPUT_KEYBOARD;   //// Setting up a generic keyboard event.
vips.ki.wScan = 0;   //// Ignoring the scan code, because the virtual-keycode will be provided instead.
vips.ki.time = 0;    //// Ignoring the timestamp, so the system will fill it for me.
vips.ki.dwExtraInfo = 0;   //// I have no extra info to provide.
//// Simulating a "Ctrl" key press.
vips.ki.wVk = 0x11;    //// Virtual-key code for the "Ctrl" key.
vips.ki.dwFlags = 0;   //// 0 means a normal key press (key going downwards).
SendInput(1, &vips, sizeof(INPUT));
//// Simulating a "C" key press.
vips.ki.wVk = 0x43;    //// Virtual-key code for the "C" key.
vips.ki.dwFlags = 0;
SendInput(1, &vips, sizeof(INPUT));
//// Simulating a "C" key release.
vips.ki.dwFlags = KEYEVENTF_KEYUP;   //// KEYEVENTF_KEYUP means a key release (key going upwards).
SendInput(1, &vips, sizeof(INPUT));
//// Simulating a "Ctrl" key release.
vips.ki.wVk = 0x11;    //// Virtual-key code for the "Ctrl" key.
SendInput(1, &vips, sizeof(INPUT));
TRACE(L"\tActive window should have received a Ctrl+C.\n");

此时,子窗口始终关闭。但是,可以肯定的是,我也留下了一个有条件的 TerminateProcess() - 如果调用它,它可能会以损坏的视频记录结束子进程。

尝试但失败的方法

GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);     //// Never worked.
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0); //// Never worked.
msgResult = wFFmp_ptr->SendMessage(WM_CHAR, 0x03, 1);    // Never worked.
msgResult = wFFmp_ptr->SendMessage(WM_UNICHAR, 0x03, 1); // Never worked.
msgResult = wFFmp_ptr->SendMessage(WM_KEYDOWN, 0x03, 1);  // Not these two.
msgResult = wFFmp_ptr->SendMessage(WM_KEYUP, 0x03, 1);
msgResult = wFFmp_ptr->SendMessage(WM_KEYDOWN, 0x11, 1); // Or these four either.
msgResult = wFFmp_ptr->SendMessage(WM_KEYDOWN, 0x43, 1);
msgResult = wFFmp_ptr->SendMessage(WM_KEYUP, 0x43, 1);
msgResult = wFFmp_ptr->SendMessage(WM_KEYUP, 0x11, 1);

这些都没有设法关闭子进程。不止一次。即使我对它们寄予厚望,因为它们会将 Ctrl+C 信号定向到我的特定窗口。因此,假设更可靠。 :-(

虽然 SendInput 方法似乎一直有效,但我对这种方法并不满意,因为它将模拟的击键发送到最前面的窗口,而不是特定的窗口。因此,它更容易出错。用户的鼠标点击或真正的键盘操作可以激活另一个窗口,然后该窗口将接收用于 FFmpeg 控制台窗口的模拟 Ctrl+C 击键。
目前是可以接受的。

【讨论】:

    猜你喜欢
    • 2013-03-03
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 2017-10-02
    • 1970-01-01
    • 1970-01-01
    • 2012-07-26
    • 2010-12-02
    相关资源
    最近更新 更多