Click here to Skip to main content
15,868,440 members
Articles / Desktop Programming / MFC
Article

An Introduction to Processes: Asynchronous Process Notification

Rate me:
Please Sign up or sign in to vote.
4.83/5 (11 votes)
16 May 2000 179.4K   2.1K   103   27
Learn how to create new processes and how to efficiently manage them.

Introduction

One of the questions that often comes up on the forum is the question about "How do I run another program?" This is often supplemented by a line like "I read about processes but they seem too complex". Well, sorry, you don't have a choice. You need to launch a process. This essay discusses several of the issues of process management. This is written primarily for C++/MFC programmers. This discussion supplements the discussion in our book, Win32 Programming, but you can use the information here without needing the book.

Creating a Process

There are many answers to this, depending on what you need to accomplish. The old-fashioned C functions spawn and system still work. However, these are considered somewhat obsolete. They don't give you the control you need to receive a notification that the process completed, because you can't get the process handle.

The underlying Win32 API call to spawn a process is ::CreateProcess. This also gives you the ability to specify, for a windowing application, where on the screen the window will appear. However, ::CreateProcess is the lowest-level interface to process spawning. Microsoft recommends you use ShellExecute, which is still not good enough; while it provides the high-level interface for the best integration into the Windows environment (for example, you can give it a URL and it will launch Internet Explorer automatically if it is not running, or send the request directly to a running instance) it still doesn't provide what you need to receive a notification.

To determine if a process has stopped, you will need a process handle. This is the token Win32 uses to represent a process to an application. You can get the process handle by using either ::CreateProcess or ::ShellExecuteEx. For the best integration into Windows, Microsoft suggests (urges, demands) that you use ::ShellExecute.

Here are two examples of how to get the process handle and store it in a variable hProcess: In both cases, the functions are called with the name of the program to launch and a pointer to any arguments for its command line. If there are no arguments, the argument pointer can be NULL. The functions return a HANDLE to the process that was created, or NULL if they failed to create a process. If they return NULL, the caller can call ::GetLastError() to determine what went wrong. Note that these are "bare bones" launchers; if you want fancy control of position, startup state, console state, initial view, etc. you can work theme-and-variations on these schemes. If you want to launch a console-mode program and feed information to stdin or receive data from stdout, you will have to use ::CreateProcess, but that's the subject of another essay.

HANDLE launchViaCreateProcess(LPCTSTR program, LPCTSTR args)
{
    HANDLE hProcess = NULL;
    PROCESSINFO processInfo;
    STARTUPINFO startupInfo;
    ::ZeroMemory(&startupInfo, sizeof(startupInfo));
    startupInfo.cb = sizeof(startupInfo);
    if(::CreateProcess(program, (LPTSTR)args, 
                       NULL,  // process security
                       NULL,  // thread security
                       FALSE, // no inheritance
                       0,     // no startup flags
                       NULL,  // no special environment
                       NULL,  // default startup directory
                       &startupInfo,
                       &processInfo))
    { /* success */
        hProcess = processInfo.hProcess;
    } /* success */
    return hProcess;
}

HANDLE launchViaShellExecute(LPCTSTR program, LPCTSTR args)
{
    HANDLE hProcess = NULL;
    SHELLEXECUTEINFO shellInfo;
    ::ZeroMemory(&shellInfo, sizeof(shellInfo));
    shellInfo.cbSize = sizeof(shellInfo);
    shellInfo.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
    shellInfo.lpFile = program;
    shellInfo.lpParameters = args;
    if(::ShellExecuteEx(&shellInfo))
    { /* success */
        hProcess = shellInfo.hProcess;
    } /* success */
    return hProcess;
}

The nature of Windows is that a launched process takes on a life of its own. If you have a Unix background, there is nothing like the "process groups" of Unix. Once a process is launched, it has a life of its own. You have explicit control of it if you retain the process handle (and the window handle, if you get that), but if your process dies, any processes you started keep right on running. The aliveness or deadness of your process has no effect on them, unless of course they were waiting for your process to do something for them (like supply stdin text). In this case, they won't die, but they will block waiting for the desired event. So if you want to terminate a process, you have to provide a way of accomplishing this.

Killing a Process

You might think that the way to terminate a process is to call the obvious API call, ::TerminateProcess. Wrong. Bad Move.

When you call ::TerminateProcess, the process stops. No matter what it is doing, it dies. Instantly. If it has a semaphore locked, or a mutex, or is in the middle of kernel code, or doing something else important, too bad. Boom! No more process. Imagine lots of Hollywood special effects with massive fireballs. Not a nice way to die.

A process should always have a "clean" way to be shut down. If you have hold of the handle of a process that has a window, you can send it a WM_CLOSE message via PostMessage to that window. If it is a console app, you should provide a way for it to shut down, such as detecting EOF on stdin, or receiving a particular text string. But don't use ::TerminateProcess unless you are willing to live with potentially serious consequences.

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. The link is at the top of the article.

// 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.


The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this article.
Copyright © 1999 The Joseph M. Newcomer Co. All Rights Reserved.
www.flounder.com/mvp_tips.htm

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Retired
United States United States
PhD, Computer Science, Carnegie Mellon University, 1975
Certificate in Forensic Science and the Law, Duquesne University, 2008

Co-Author, [i]Win32 Programming[/i]

Comments and Discussions

 
AnswerRe: can not compile Pin
kezhu28-Feb-06 16:57
kezhu28-Feb-06 16:57 
GeneralRe: can not compile Pin
wanghuabin201024-Mar-13 6:09
wanghuabin201024-Mar-13 6:09 
GeneralHWND Pin
imod23-Jun-04 20:32
imod23-Jun-04 20:32 
QuestionHOW TO WAIT ALL THE CHILD THREAD Pin
shihao8-Apr-04 17:19
shihao8-Apr-04 17:19 
AnswerRe: HOW TO WAIT ALL THE CHILD THREAD Pin
Joseph M. Newcomer8-Apr-04 18:23
Joseph M. Newcomer8-Apr-04 18:23 
GeneralBad mask flag, syntax error (SEE_MASK_FLAG_NO_UI) Pin
Ondrej Novotny21-May-03 23:10
Ondrej Novotny21-May-03 23:10 
GeneralRe: Bad mask flag, syntax error (SEE_MASK_FLAG_NO_UI) Pin
Joseph M. Newcomer22-May-03 3:56
Joseph M. Newcomer22-May-03 3:56 
QuestionPostMessage? Pin
wilche5-Jun-02 21:22
wilche5-Jun-02 21:22 
AnswerRe: PostMessage? Pin
Joseph M. Newcomer6-Jun-02 2:21
Joseph M. Newcomer6-Jun-02 2:21 
QuestionHow can i start a process with SW_HIDE ??????? Pin
12-Feb-02 22:25
suss12-Feb-02 22:25 
AnswerRe: How can i start a process with SW_HIDE ??????? Pin
Sreekanth Muralidharan27-Mar-06 0:46
Sreekanth Muralidharan27-Mar-06 0:46 
GeneralGet process handle by name in NT Pin
Chandran23-Jan-02 7:00
Chandran23-Jan-02 7:00 
GeneralRe: Get process handle by name in NT Pin
14-Jun-02 14:17
suss14-Jun-02 14:17 
Generalqussion Pin
25-Oct-01 7:22
suss25-Oct-01 7:22 
QuestionHow can I Kill a process, who launchs another process, and it's child process? Pin
7-Aug-01 17:38
suss7-Aug-01 17:38 
GeneralJust a suggestion.. Pin
Peter18-Aug-00 21:42
Peter18-Aug-00 21:42 
GeneralRe: Just a suggestion.. Pin
beetung7-Apr-04 7:41
beetung7-Apr-04 7:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.