【问题标题】:GenerateConsoleCtrlEvent crashes when child process is cmd子进程为 cmd 时 GenerateConsoleCtrlEvent 崩溃
【发布时间】:2015-06-12 10:00:19
【问题描述】:

我正在尝试将基本的 Windows 进程功能包装在进程类中。 我希望进程在父进程的同一个进程组和同一个控制台中运行,并且我想在调用 Process::Kill(); 时轻轻地杀死它们;

从各种来源阅读,我最终得到了这段代码,它首先检查进程是否是 GUI,如果是则发送 WM_CLOSE(EnumChildWindowsHandler 执行),如果不是,并且进程有控制台,则发送CTRL_C 事件。它可以工作,但是当子进程是“cmd.exe”时,该过程在 GenerateConsoleCtrlEvent 函数上崩溃,并且调试器说发生了写入访问冲突。

有什么意义?我在阅读中没有理解什么?

bool Process::Kill( ) {  
  // Here I check if the process is a GUI app  
  if (!EnumThreadWindows(mChildInfo->dwThreadId, EnumChildWindowsHandler,  mChildInfo->dwProcessId)) {
    if (WaitForSingleObject(mChildInfo->hProcess, 2000) == WAIT_TIMEOUT) {
      { TerminateProcess(mChildInfo->hProcess, 0); }  }

  //If not, test if it's a CUI then send CTRL_C
  else { 
    int minPid = 10;
    int els;
    unsigned long *pids = new unsigned long(minPid);
    els =  GetConsoleProcessList( pids, minPid );
    if (els > minPid) {
      free (pids);
      pids = new unsigned long(els);
      els = GetConsoleProcessList(pids, els);
    }

    if (find(pids, pids+els, mChildInfo->dwProcessId)) {
      cout << "Sending CTRL_C_EVENT.." << endl;

      SetConsoleCtrlHandler(NULL, TRUE);
      GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
      if (WaitForSingleObject(mChildInfo->hProcess, 2000) == WAIT_TIMEOUT) { TerminateProcess(mChildInfo->hProcess, 0); }
      SetConsoleCtrlHandler(NULL, FALSE);  

      CloseHandle(mChildInfo->hProcess);
      CloseHandle(mChildInfo->hThread);
    } 
    return true;   
  }

编辑:我找到了 ReactOS cmd.exe 源代码,它是这类事情的黄金。我将发布我最终学到的更新。 网址:http://doxygen.reactos.org/db/d4f/base_2shell_2cmd_2cmd_8c_source.html

【问题讨论】:

  • 我无法重现该错误,但 cmd.exe 控制处理程序 cmd!Handler 通常会忽略 CTRL+CCTRL+BREAK,除非在运行批处理文件时。在后一种情况下,它会显示提示符"Terminate batch job (Y/N)?"。在您的情况下,cmd 是使用交互式命令提示符、单个 /c 命令运行还是执行批处理文件?顺便说一句,除非手动启用,否则进程组(即CREATE_NEW_PROCESS_GROUP)已禁用CTRL+C,因此使用CTRL_BREAK_EVENT会更可靠。但是您需要在自己的进程中设置一个忽略 CTRL+BREAK 的实际处理程序。
  • 如果您确实使用CREATE_NEW_PROCESS_GROUP 创建子进程,您应该只为子进程的 PID 生成事件,它兼作进程组 ID。这样,只有子进程及其创建的任何控制台进程才能看到该事件。如果它连接到同一个控制台,则为组 0 生成事件甚至会将​​其发送到您自己的父进程。这可能会产生意想不到的后果。
  • 子进程只是在父控制台中运行的“cmd.exe”的一个实例,不会创建新组(因为组阻止发送 CTRL_C)。也许我应该看看 GenerateConsoleInput。或者我可以在新控制台中运行每个程序,将流重定向到我的控制台,然后将 CTRL_C 发送到分离的进程。顺便说一句,我没有找到关于 CTRL_C 和 CTRL_BREAK 差异的很好解释,最后一个应用程序是否也会轻轻终止?
  • 一个进程有一个默认的控制事件处理程序kernel32!DefaultHandler,它调用ExitProcess。在这种情况下,将使用DLL_PROCESS_DETACH 调用 DLL 入口点,因此例如,将调用 CRT atexit 函数。也就是说,一个进程可以链接多个事件处理程序,而不必链接到默认处理程序。例如,CRT 安装一个处理程序,用于为 CTRL+CCTRL+BREAK 事件引发 SIGINTSIGBREAK

标签: c++ windows process console signals


【解决方案1】:

cmd 源代码很好地解释了如何正确执行,顺便说一句,我只是使用 CTRL_BREAK 并且像一个魅力一样工作。在创建过程中,我通过 CREATE_NEW_PROCESS_GROUP 并将 SW_SHOWDEFAULT 设置为 showWindow 模式,然后以这种方式终止进程:

//Determine if lParam has for main window the hWnd, and send WM_CLOSE
int CALLBACK EnumChildWindowsHandler(HWND hWnd, LPARAM lParam) 
{ 
  //WORD type
  unsigned long pid = 0;
  GetWindowThreadProcessId(hWnd, &pid);

  if (pid == (unsigned long) lParam) {
    cout << "HANDLEWIN: "  << hWnd <<  " Bwehe" << endl;
    cout << "PID: " << pid << endl;

    if (!GetParent(hWnd)) PostMessageA(hWnd, WM_CLOSE, 0, 0);
    return 0;
  }
  return 1; 
}

bool Process::KillGently( ) {
  if (EnumThreadWindows(mProcInfo->dwThreadId, EnumChildWindowsHandler,  mProcInfo->dwProcessId)) {
     if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, mProcInfo->dwProcessId))
      cerr << "Error while sending CTRL_BREAK_EVENT: " << GetLastError() << endl;
  }

另外,如果我们想在处理信号时以类似 cmd 的方式操作,我们可以像这样注册一个 sighandler(我们仍然需要 WriteConsoleInput):

BOOL signal_handler(DWORD signum) {

  INPUT_RECORD iRecord;

  if (signum != CTRL_C_EVENT && signum != CTRL_BREAK_EVENT ) return FALSE;

  if(!TryEnterCriticalSection(&mChildRunningLock)) {
     GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, mChild.mProcInfo->dwProcessId);
     mIsBreakHandled = true;
     return TRUE;
  }
  else  LeaveCriticalSection(&mChildRunningLock);

  iRecord.EventType = KEY_EVENT;
  iRecord.Event.KeyEvent.bKeyDown = TRUE;
  iRecord.Event.KeyEvent.wRepeatCount = TRUE;
  iRecord.Event.KeyEvent.wVirtualKeyCode = _T('C');
  iRecord.Event.KeyEvent.wVirtualScanCode = _T('C') - 35;
  iRecord.Event.KeyEvent.uChar.AsciiChar = _T('C');
  iRecord.Event.KeyEvent.uChar.UnicodeChar = _T('C');
  iRecord.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED;

  WriteConsoleInputA(GetStdHandle(STD_INPUT_HANDLE), &iRecord, 1, 0);
  mIsBreakHandled = true;
  return TRUE;
}

主线程进入临界区,等待刚刚启动的进程终止。

顺便说一句,我仍然不知道为什么我的应用程序会因之前的代码而崩溃。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-29
    • 1970-01-01
    • 2012-10-26
    • 1970-01-01
    • 1970-01-01
    • 2020-11-11
    • 2021-06-29
    • 1970-01-01
    相关资源
    最近更新 更多