Link to home
Start Free TrialLog in
Avatar of KTN-IT
KTN-ITFlag for United States of America

asked on

How to add an option to terminate an infinite loop?

At my organization, I am turning old XP machines into remote desktop clients by changing the Windows shell (at HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell) from explorer.exe to mstsc.exe.

Below is some code that starts a process (notepad, in this case) and then waits for it to be terminated, and starts it again.  This is what I want for my remote desktop clients, so when I user logs off from remote desktop, mstsc starts again to wait for another user to log in.

At some point, though, the process must be terminated, for instance, when I want to shut down or restart the computer.  How would I build in some functionality that would allow an exit from the infinite loop?  For instance, a certain key combination.
#include <windows.h>

void main ()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&pi, 0, sizeof(pi));
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);
    TCHAR sCommandLine[MAX_PATH] = TEXT("C:\\windows\\NOTEPAD.EXE");

    BOOL result = 0;

    result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    MSG msg;
    while(1) // infinite loop
    {
        while (WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1,&pi.hProcess,TRUE,INFINITE,QS_ALLINPUT))
        {
          while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) DispatchMessage(&msg);
        }
        result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    }
}

Open in new window

Avatar of kaufmed
kaufmed
Flag of United States of America image

Maybe I'm missing a detail, but wouldn't you just change the "while(1)" to "while(condition_variable)"? Until your condition variable becomes 0, the loop will run, so you just need to determine what your exit condition will be and then set the condition_variable to zero when you want to exit.
If you shut down your computer,the process will be terminated anyway. But, in case you want to perform any cleanup actions or such, you can use 'SetConsoleCtrlHandler()' (http://msdn.microsoft.com/en-us/library/ms686016%28VS.85%29.aspx) to be notified about that, e.g.
#include <windows.h>

BOOL WINAPI CtrlHandlerRoutine(DWORD dwCtrlType)
{

  switch(dwCtrlType)
  {
    case CTRL_SHUTDOWN_EVENT:
    case CTRL_LOGOFF_EVENT:

      ExitProcess(0); // also terminates loop
  }

  return TRUE;
}

void main ()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&pi, 0, sizeof(pi));
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);
    TCHAR sCommandLine[MAX_PATH] = TEXT("C:\\windows\\NOTEPAD.EXE");

    SetConsoleCtrlHandler(CtrlHandlerRoutine,TRUE);

    BOOL result = 0;

    result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    MSG msg;
    while(1) // infinite loop
    {
        while (WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1,&pi.hProcess,TRUE,INFINITE,QS_ALLINPUT))
        {
          while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) DispatchMessage(&msg);
        }
        result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    }
}

Open in new window

Avatar of KTN-IT

ASKER

My thinking was more along the lines of kaufmed's suggestion.  Some way to set a condition upon which the loop would exit.  And no, kaufmed, you're not missing anything, other than the fact that I am a complete C++ newbie, and I don't know how to do much.

jkr, from what I read in the link you provided, 'SetConsoleCtrlHandler()' would just notify me when the program was forcibly terminated, due to Ctrl-C or something.  I'm looking for something slightly more graceful than that, like a key combination or something.

How could a link a condition in my soon-to-be-not-infinite loop to some keyboard input?  That probably should have been my original question.
Actually, the handler routine is to notify you of shutdown or logoff events, along with CTRL+C. Note that you can also send your own control events which you then in turn can handle. If you oprefer a condition variable though, you could
#include <windows.h>

BOOL bRunning = TRUE; // global condition

BOOL WINAPI CtrlHandlerRoutine(DWORD dwCtrlType)
{

  switch(dwCtrlType)
  {
    case CTRL_SHUTDOWN_EVENT:
    case CTRL_LOGOFF_EVENT:

      bRunning = FALSE; // terminates loop
  }

  return TRUE;
}

void main ()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&pi, 0, sizeof(pi));
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);
    TCHAR sCommandLine[MAX_PATH] = TEXT("C:\\windows\\NOTEPAD.EXE");

    SetConsoleCtrlHandler(CtrlHandlerRoutine,TRUE);

    BOOL result = 0;

    result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    MSG msg;
    while(bRunning) // global condition
    {
        while (WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1,&pi.hProcess,TRUE,INFINITE,QS_ALLINPUT))
        {
          while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) DispatchMessage(&msg);
        }
        result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    }
}

Open in new window

Avatar of KTN-IT

ASKER

Thanks, jkr.  I like the global variable concept.

Considering that this application is actually going to be a replacement shell instead of explorer.exe, I'm not sure there will be much chance for anyone to shutdown or terminate it except with ctrl-alt-del.

Is it difficult to add some kind of wait for a user-entered key combination that would alter the global variable?
Avatar of KTN-IT

ASKER

I should also add that I've got this running now as a Windows program, not a console program.  (But there aren't any Windows created.)
Avatar of KTN-IT

ASKER

Here's my code now:

#include <windows.h>

BOOL bRunning = TRUE; // global condition

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&pi, 0, sizeof(pi));
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);
    TCHAR sCommandLine[MAX_PATH] = TEXT("C:\\windows\\NOTEPAD.EXE");

    BOOL result = 0;

    result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    MSG msg;
    while(bRunning)
    {
        while (WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1,&pi.hProcess,FALSE,INFINITE,QS_ALLINPUT))
        {
          while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) DispatchMessage(&msg);
        }
        result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    }
    return 0;
}

Open in new window

>>I should also add that I've got this running now as a Windows program

Then you can handle WM_ENDSESSION (http://msdn.microsoft.com/en-us/library/aa376889(VS.85).aspx) and WM_QUERYENDSESSION ("http://msdn.microsoft.com/en-us/library/aa376890(VS.85).aspx") the same way.
Um, actually you'll need a separate thread to monitor a process in a GUI app, since the main thread will be busy handling the messages (and then the global variable concept should be altered if stuff becomes more complex later), e.g.
#include <windows.h>

#pragma comment(lib,"user32.lib")

static char g_szClassName[] = "MyWindowClass";
static HINSTANCE g_hInst = NULL;

BOOL bRunning = TRUE; // global condition

DWORD WINAPI MonitorThread(LPVOID) {

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&pi, 0, sizeof(pi));
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(si);
    TCHAR sCommandLine[MAX_PATH] = TEXT("C:\\windows\\NOTEPAD.EXE");

    BOOL result = 0;

    result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    MSG msg;
    while(bRunning)
    {
        while (WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1,&pi.hProcess,FALSE,INFINITE,QS_ALLINPUT))
        {
          while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) DispatchMessage(&msg);
        }
        result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    }
    return 0;
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
  switch(Message)
  {
     case WM_ENDSESSION:
     case WM_QUERYENDSESSION:
     bRunning = FALSE;
     break;
     case WM_CLOSE:
        DestroyWindow(hwnd);
     break;
     case WM_DESTROY:
        PostQuitMessage(0);
     break;
     default:
        return DefWindowProc(hwnd, Message, wParam, lParam);
  }
  return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX WndClass;
  HWND hwnd;
  MSG Msg;

  g_hInst = hInstance;

  WndClass.cbSize        = sizeof(WNDCLASSEX);
  WndClass.style         = NULL;
  WndClass.lpfnWndProc   = WndProc;
  WndClass.cbClsExtra    = 0;
  WndClass.cbWndExtra    = 0;
  WndClass.hInstance     = g_hInst;
  WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
  WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  WndClass.lpszMenuName  = NULL;
  WndClass.lpszClassName = g_szClassName;
  WndClass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

  if(!RegisterClassEx(&WndClass))
  {
     MessageBox(0, "Window Registration Failed!", "Error!",
        MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL);
     return 0;
  }

  hwnd = CreateWindowEx(
     WS_EX_CLIENTEDGE,
     g_szClassName,
     "The title of my window",
     WS_OVERLAPPEDWINDOW,
     CW_USEDEFAULT, CW_USEDEFAULT, 320, 240,
     NULL, NULL, g_hInst, NULL);

  if(hwnd == NULL)
  {
     MessageBox(0, "Window Creation Failed!", "Error!",
        MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL);
     return 0;
  }

  ShowWindow(hwnd, SW_HIDE); // invisible window
  UpdateWindow(hwnd);

  while(GetMessage(&Msg, NULL, 0, 0))
  {
     TranslateMessage(&Msg);
     DispatchMessage(&Msg);
  }
  return Msg.wParam;
}

Open in new window

Avatar of KTN-IT

ASKER

The Windows code I posted actually worked for me, after I changed the third parameter of MsgWaitForMultipleObjects to FALSE.

I ran the code you posted above, but it didn't do anything.  It just sat there.  There were no errors, but notepad never came up.

So what you're saying is, if I want to monitor keystrokes, I need a separate process? do I have to have a window to do it?
Sorry, forgot the code to create the thread - that would be
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX WndClass;
  HWND hwnd;
  MSG Msg;

  g_hInst = hInstance;

  WndClass.cbSize        = sizeof(WNDCLASSEX);
  WndClass.style         = NULL;
  WndClass.lpfnWndProc   = WndProc;
  WndClass.cbClsExtra    = 0;
  WndClass.cbWndExtra    = 0;
  WndClass.hInstance     = g_hInst;
  WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
  WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  WndClass.lpszMenuName  = NULL;
  WndClass.lpszClassName = g_szClassName;
  WndClass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

  if(!RegisterClassEx(&WndClass))
  {
     MessageBox(0, "Window Registration Failed!", "Error!",
        MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL);
     return 0;
  }

  hwnd = CreateWindowEx(
     WS_EX_CLIENTEDGE,
     g_szClassName,
     "The title of my window",
     WS_OVERLAPPEDWINDOW,
     CW_USEDEFAULT, CW_USEDEFAULT, 320, 240,
     NULL, NULL, g_hInst, NULL);

  if(hwnd == NULL)
  {
     MessageBox(0, "Window Creation Failed!", "Error!",
        MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL);
     return 0;
  }

  DWORD dwTID;

  CreateThread(NULL,0,MonitorThread,NULL,0,&dwTID); // create monito thread

  ShowWindow(hwnd, SW_HIDE); // invisible window
  UpdateWindow(hwnd);

  while(GetMessage(&Msg, NULL, 0, 0))
  {
     TranslateMessage(&Msg);
     DispatchMessage(&Msg);
  }
  return Msg.wParam;
}

Open in new window

Avatar of KTN-IT

ASKER

I inserted your updated code.  The result is that now notepad is started once.  But when I close it it does not restart.

No thoughts on the key combination monitoring idea?
Avatar of KTN-IT

ASKER

I'll be back on Monday to work on this.

Thanks for all your help!!!!!
ASKER CERTIFIED SOLUTION
Avatar of jkr
jkr
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of KTN-IT

ASKER

OK, thanks. The code works now.  However, it does the same thing that the code I posted at the beginning of this question does.  How do I take advantage of the global variable to exit my loop?
Well, that is done in the following snippets from the above code:
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
  switch(Message)
  {
     case WM_ENDSESSION:
     case WM_QUERYENDSESSION:
     bRunning = FALSE; // <------------ cause loop to exit
     break;
     case WM_CLOSE:
        DestroyWindow(hwnd);
     break;
     case WM_DESTROY:
        PostQuitMessage(0);
     break;
     default:
        return DefWindowProc(hwnd, Message, wParam, lParam);
  }
  return 0;
}

//....

    while(bRunning) // <-------- exit if variable was changed in the message handler
    {
        WaitForSingleObjects(pi.hProcess,INFINITE)
        
        result = CreateProcess(NULL, sCommandLine,NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
    }

Open in new window

Avatar of KTN-IT

ASKER

How would I send WM_QUERYENDSESSION to my app/window?  Do I need the window to be visible?  Or can the invisible window monitor key strokes?
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of KTN-IT

ASKER

I'm still not clear on how I can tie this in to a user key stroke combination, but I've learned a whole lot from these answers.  Thank you!