GenerateConsoleCtrlEvent crashes when child process is cmd

1.7k Views Asked by At

I'm trying to wrap basic windows process functionalities in a Process Class. I want the processes to run in the same process group and same console of the parent, and I want to kill them gently when I call Process::Kill();

Reading from various sources I've ended up with this code, which first checks if the process is a GUI, if so sends a WM_CLOSE (the EnumChildWindowsHandler does it), if not, and the process has a console, sends a CTRL_C event. It works, but when the child Process is the "cmd.exe" the procedure crashes on the GenerateConsoleCtrlEvent function and the debugger says an access violation on write happened.

What's the point? What didn't I understand in my readings?

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;   
  }

EDIT: I've found the ReactOS cmd.exe source code which is gold for this sort of thing. I'll post updates from what I eventually learn. Url: http://doxygen.reactos.org/db/d4f/base_2shell_2cmd_2cmd_8c_source.html

1

There are 1 best solutions below

0
On

The cmd source code explains well how to do correctly, BTW I just use the CTRL_BREAK and works like a charm. In the create process I pass CREATE_NEW_PROCESS_GROUP and I set the SW_SHOWDEFAULT for the showWindow mode, and then I kill processes this way:

//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;
  }

Also, if we want to act in a cmd like way while handling signals, we can register a sighandler like this (we still nedd the 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;
}

The main thread enters the critical section while waiting for the termination for the process just started.

BTW I still don't know why my application was crashing with the previous code.