簡體   English   中英

將控制字符發送到子進程的 STDIN 的可靠方法是什么?

[英]What is a reliable way to send control characters to the STDIN of a child process?

背景
我正在構建一個基本的 MFC 應用程序,它可以幫助我運行測試。 GUI 非常簡潔。 一個按鈕開始測試,另一個按鈕停止測試,加上一個靜態文本對象,顯示幾個字母和數字,指示測試正在執行的階段。 這幾個星期以來一直令人滿意。
如果我的應用程序在執行測試時還進行了視頻屏幕錄制,我會發現它很有用。 大計划是將FFmpeg庫函數調用編譯到代碼中以實現此功能,但這肯定需要我數周時間來學習、嘗試和完成。 與此同時,一個快速但足夠的解決方案是從下載的 Windows 二進制版本中調用預編譯的ffmpeg.exe
我在::OnBnClickedButtonStarttest()方法中添加了代碼以通過CreateProcess()調用ffmpeg.exe 這工作正常。 FFmpeg屏幕錄制過程在一個新的控制台窗口中開始,並且可以很好地完成其預期工作。 當我選擇該控制台窗口並按Ctrl+C 時,錄制停止並且我擁有所需的視頻文件。 我還在::OnBnClickedButtonStoptest()方法中添加了代碼,以將所需的Ctrl+C發送到ffmpeg.exe進程的 STDIN 並在單擊 STOP 按鈕時完成錄制。 這也工作正常。 大多數時候
我的源代碼的這兩部分基於在https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-上發布的示例微軟的輸出

問題
大多數時間停止屏幕錄制過程意味着它不會總是停止。 有時解決方案不起作用。 而且我找不到任何原因為什么大多數時間都有效的相同解決方案在其他時候會失敗。 最終我更願意隱藏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/ccc-console-redirection-with-sockets-win32.62350/
這似乎使用自己的命名通道來訪問子進程的標准輸入。 我更喜歡這種方法。 不幸的是,它不僅不能工作,而且不能編譯。 Socket thisSocket; line 指的是一個不存在的類型。 沒有像Socket這樣的類型,沒有大寫的S和小寫的ocket 我試圖在它的位置使用所有大寫SOCKET類型,它可以編譯,但相應地修改了WriteFile((HANDLE)thisSocket, chBuf, dwNumOfBytesToWrite, &dwWritten, NULL); 行始終返回 FAILURE,並且不會停止子進程。 為了使我的調查更加困難,大量的互聯網搜索結果將我帶到了 WINSOCK 相關主題。

我錯過了什么?
為了巧妙地要求它完成錄制,向 ffmpeg.exe 進程發送 Ctrl+C 的可靠方法是什么?
請注意,我可以通過TerminateProcess(g_ffmpeg_process_handle, 0)殘酷地殺死FFmpeg控制台窗口,但這並不總是允許孩子正確關閉視頻文件,從而導致屏幕錄制損壞。

沒有合適的解決方案,但我有一個可以接受的解決方法。

我引入了一個初始值為零的全局變量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,直到我遇到了我的FFmpeg的子進程,其中的進程ID 的CreateProcess()在它的dwProcessId成員返回PROCESS_INFORMATION結構爭論。
我找不到更好的方法來從其已知進程 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 擊鍵。
目前來說是可以接受的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM