Process Management

When did it stop?

Often you will want to launch a process, often a console application, and let it run until it completes. When it completes, you can then deal with its results. For example, I have a case where I spawn (of all things) a 16-bit compiler (it is written in assembly code, and no, I had nothing to do with it; I just had to use it in a client app). I spawn it with a commandline

  compilername inputfile, listingfile, outputfile

and I have to wait for it to complete before I can let the user examine the listing file or download the output file.

This is any easy one, because the compiler works with very tiny programs, and runs in under 5 seconds. So for this application, I just wait for it to complete. 

HANDLE process = launcher_of_your_choice(program, args);
if(process != NULL)
    { /* success */
     ::WaitForSingleObject(process, INFINITE);
     ::CloseHandle(process);
    } /* success */

However, not all programs have this property. In this case, you want to get an asynchronous notification of the completion. I do this by what appears to be a complex method, but in fact is very simple: I spawn a thread that blocks on the process handle. When the process completes, the thread resumes execution, posts a message to my main GUI window, and terminates.

I'm reproducing the code for the WaitInfo class here because it is so small. This is also part of a demo project you can download from this site.

WaitInfo.h

class WaitInfo {
    public:
       WaitInfo() {hProcess = NULL; notifyee = NULL; }
       virtual ~WaitInfo() { }
       void requestNotification(HANDLE pr, CWnd * tell);
       static UINT UWM_PROCESS_TERMINATED;
    protected:
       HANDLE hProcess; // process handle
       CWnd * notifyee; // window to notify
       static UINT waiter(LPVOID p) { ((WaitInfo *)p)->waiter(); return 0; }
       void waiter();
};

/****************************************************************************
*                           UWM_PROCESS_TERMINATED
* Inputs:
*       WPARAM: ignored
*       LPARAM: Process handle of process
* Result: LRESULT
*       Logically void, 0, always
* Effect: 
*       Notifies the parent window that the process has been terminated
* Notes:
*       It is the responsibility of the parent window to perform a
*       ::CloseHandle operation on the handle. Otherwise there will be
*       a handle leak.
****************************************************************************/
#define UWM_PROCESS_TERMINATED_MSG 
_T("UWM_PROCESS_TERMINATED-{F7113F80-6D03-11d3-9FDD-006067718D04}")

WaitInfo.cpp

// 
#include "stdafx.h"
#include "WaitInfo.h"
UINT WaitInfo::UWM_PROCESS_TERMINATED = 
::RegisterWindowMessage(UWM_PROCESS_TERMINATED_MSG); /**************************************************************************** * WaitInfo::requestNotification * Inputs: * HANDLE pr: Process handle * CWnd * wnd: Window to notify on completion * Result: void * * Effect: * Spawns a waiter thread ****************************************************************************/ void WaitInfo::requestNotification(HANDLE pr, CWnd * wnd) { hProcess = pr; notifyee = wnd; AfxBeginThread(waiter, this); } // WaitInfo::requestNotification /**************************************************************************** * WaitInfo::waiter * Result: void * * Effect: * Waits for the thread to complete and notifies the parent ****************************************************************************/ void WaitInfo::waiter() { ::WaitForSingleObject(hProcess, INFINITE); notifyee->PostMessage(UWM_PROCESS_TERMINATED, 0, (LPARAM)hProcess); } // WaitInfo::waiter

The way this is used is that after you have created your process, you call the requestNotification method to request a notification. You pass in the handle of the process and the window which is to receive the notification. When the process terminates, a notification message is sent to the specified window. You must have a WaitInfo object that is created before the requestNotification is called and remains valid until the notification message is received; this means that it cannot be a variable on the stack. In the example code I provide, I put it in the class header of the window class that launches the program.

In the header file for my class, I add the following:

WaitInfo requestor;
afx_msg LRESULT OnCompletion(WPARAM, LPARAM)

In the MESSAGE_MAP of the window, you need to add a line for the handler. Because this uses a qualified name, the ClassWizard is emotionally unprepared to deal with it, so you have to place it as shown, after the //}}AFX_MSG_MAP line.

    //}}AFX_MSG_MAP
    ON_REGISTERED_MESSAGE(WaitInfo::UWM_PROCESS_COMPLETED, OnCompletion)
END_MESSAGE_MAP()

After I launch the process, I do

    HANDLE process = launcher_of_your_choice(program, args);
    if(process != NULL)
       { /* success */
        requestor.requestNotification(process, this);
       } /* success */

The handler is quite simple:

LRESULT CMyClass::OnCompletion(WPARAM, LPARAM lParam)
    {
     // whatever you want to do here
     ::CloseHandle((HANDLE)lParam);
     return 0;
    }

You can study more about what I do in the sample file.

If some of the above looked confusing, you might want to read my essays on message management and worker threads.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Nine people can't make a baby in a month.” - Fred Brooks