Click here to Skip to main content
13,344,765 members (62,505 online)
Click here to Skip to main content
Add your own
alternative version


69 bookmarked
Posted 15 Oct 2001

An MFC Process Class

, 25 Oct 2001
Rate this:
Please Sign up or sign in to vote.
This class allows you to create a child process and receive notification of its output.

Child Processes with output

One of the common problems that arises is how to fire off a child process and collect its output. This class allows you to create a child process and receive notification of its output.

The technique includes the method of having a worker thread post messages to the main GUI thread, as described in my essay on worker threads.

Using the Class

The class is invoked quite simply. The class is designed so you can actually have several classes running concurrently, and sort the output from the various child classes if you need to.

Simple Usage

void CMyView::OnRun()
 CString cmd;  // the command to execute
 cmd = ...;
 Process * p = new Process(cmd, this);
    ... failed

This creates a Process object to execute the command string passed in. Notifications of events will be posted to the window passed in as the second parameter. Note that this must not be a NULL pointer. If any error occurs, or when the process completes, the Process object will be automatically deleted. The run method actually calls the CreateProcess API. Control returns immediately upon creating the process; it does not wait for process completion.

There are two events which you will need to handle in the CWnd class that receives notifications:

UPM_LINE is sent for each input line that is received, passing a CString * containing the line contents to the target window.

UPM_FINISHED notifies the window that the thread has finished, which allows the window to re-enable controls, menus, etc.

These are Registered Window Messages. The IMPLEMENT_MSG macro is used to declare them in the module in which they are used:


You must declare handlers for these in your header file:

afx_msg LRESULT OnFinished(WPARAM, LPARAM)

and install the entries in the MESSAGE_TABLE:


A typical handler is to use a CListBox as the logging control. The example uses a simple CListBox with the Sorted option disabled (your output is pretty useless most of the time if it is simply sorted alphabetically), or you may use something more elaborate such as my Logging ListBox Control.

 CString * s = (CString *)wParam;
 delete s;
 return 0;

Multiple Process Usage

If you want to use multiple processes concurrently, you need to distinguish the events. The way this is handled is that you create a unique UINT to represent a process. This ID value will be sent with every message, and you have to use it to determine which of your child processes generated the message. Note that this is an id you assign; it is not a process ID or process handle. How you sort out the results is up to you. As long as you have a unique ID for the child process, it can be dynamically generated or a simple constant.



Process(const CString & command, CWnd * target, UINT id = 0)

const CString & command

Command string to execute

CWnd * target

Target window for notification messages


Process identifier (application-generated), default is zero.

Creates a Process object and initializes it to the specified parameters. This does not create a system process, only a process object. A Process object must always be allocated from the heap, because it will be automatically destroyed when the process terminates.

BOOL run()

Creates a process and the thread to receive data from it. Control returns immediately. If the process and thread were created successfully, returns TRUE, else returns FALSE. If the value FALSE is returned, the Process object is immediately destroyed. If TRUE is returned, the Process object exists and will exist until the process terminates. Note that there are no methods that can be called after the process is created that will have any meaning, and consequently there is no reason to retain the Process object pointer once the run method has been invoked.



The process handle associated with the child process


The id value established by the Process constructor.


Logically void, 0, always.


A string object representing one line of output captured from the child process. The CR and LF have been stripped from the string. The recipient of this message is responsible for deleting this CString object when its value is no longer needed.


The id value established by the Process constructor.


Logically void, 0, always.


The error code from ::GetLastError representing the reason for failure, or 0 if there was no error.


The id value established by the Process constructor.


Logically void, 0, always.

This message is sent under two conditions: The worker thread failed to create, and thus there will be no further output delivered, or the ReadFile from the child process either returned an EOF condition or has terminated with an ERROR_BROKEN_PIPE error.

How it works

The most complex part of the operation is the creation of the process and establishment of the pipes. 


BOOL Process::run()
     hreadFromChild = NULL;

hreadFromChild is a member variable of the Process class, and is used by the worker thread to read from the child process. The other two handles, below, have no need to exist beyond this function.

HANDLE hwriteToParent = NULL;
HANDLE hwriteToParent2 = NULL;

// inheritable handle

The SECURITY_ATTRIBUTES is used to create inheritable handles; the last member of the structure is set to TRUE so that the handle will be inheritable.

 if(!::CreatePipe(&hreadFromChild, &hwriteToParent, &sa, 0))
{ /* pipe failed */
 // ::GetLastError() will reveal the cause
 delete this;
 return FALSE;
} /* pipe failed */

The CreatePipe operation creates a single unidirectional anonymous pipe, and returns handles to its read and write ends. They are, by the SECURITY_ATTRIBUTES, inheritable handles.

 if(!::DuplicateHandle(GetCurrentProcess(),     // duplicate from this process
           hwriteToParent,      // this handle
           GetCurrentProcess(),     // into this process
           &hwriteToParent2,        // as this handle
           0,               // no access flags
                              // (subsumed by DUPLICATE_SAME_ACCESS)
           TRUE,                // create inheritable
           DUPLICATE_SAME_ACCESS))  // create duplicate access
{ /* duplicate failed */
 DWORD err = ::GetLastError();
 delete this;
 return FALSE;
} /* duplicate failed */

The handle which is to be passed down for standard output will also be passed in for the standard error handler. A number of child processes, including the command interpreter, have a tendency to close the standard error handle if they are not going to use it. If we pass in the same handle for both output and error, when the child process closes the error handle, it necessarily closes the output handle. This means you could never see the output from such a process. By using DuplicateHandle, we get a duplicate handle representing the same stream. If the child process closes this duplicate handle (which we pass in as the error handle), the output handle remains active.


::ZeroMemory(&startup, sizeof(startup));

startup.cb = sizeof(startup);
startup.wShowWindow = SW_HIDE; // hidden console window
startup.hStdInput = NULL; // not used
startup.hStdOutput = hwriteToParent;
startup.hStdError = hwriteToParent2;

Here we initialize the structure. Note that in keeping with good programming practice, the structure should first be zeroed. The standard input handle is not set. If you have a child process that requires input, you would want to set it here, using a variant of this code. You would also have to create a thread to provide the data for the input stream.

// We want a non-inherited read handle. DuplicateHandle with a
// NULL target fixes the read side to be non-inheritable
::DuplicateHandle(::GetCurrentProcess(),    // in this process
          hreadFromChild,           // child read handle
          ::GetCurrentProcess(),    // to this process
          NULL,                     // modify existing handle
          0,                        // flags
          FALSE,                    // not inheritable
          DUPLICATE_SAME_ACCESS);   // same handle access

This seems a little odd; we are creating a "duplicate" without specifying a target (the fourth parameter is NULL). This is an odd idiom that is used. We want to inherit the output handle so the child process can send output, but we don't want to inherit the input side of the handle. One of the effects of DuplicateHandle is that if the target handle address is given as NULL, it modifies the input handle. By setting the inheritable characteristic to FALSE, the handle is rendered non-inheritable.

// We need a writeable buffer for the command (silly Windows restriction)
LPTSTR cmd = command.GetBuffer(command.GetLength() + 1);

The CreateProcess call requires an LPTSTR, not an LPCTSTR (constant string). Therefore, we cannot use the CString value of command as a parameter. In particular, the buffer must be modifiable. We use GetBuffer to get a modifiable buffer, and we allow space for a character than might be appended to the command line (read the CreateProcess documentation). 

BOOL started = ::CreateProcess(NULL,        // command is part of input string
               cmd,         // (writeable) command string
               NULL,        // process security
               NULL,        // thread security
               TRUE,        // inherit handles flag
               0,           // flags
               NULL,        // inherit environment
               NULL,        // inherit directory
               &startup,    // STARTUPINFO
               &procinfo);  // PROCESS_INFORMATION

The CreateProcess call is very straightforward. I have done nothing here to allow for any of the thread or process security options, modifications of the environment or directory, etc. If you need such features, you can enhance the Process constructor to supply such values and create member variables to hold them. The only difference between this CreateProcess call and other instances of the call is that the fifth parameter is TRUE, meaning all inheritable handles will be inherited by the child process. This is how we pass the standard handles to the child.


{ /* failed to start */
 DWORD err = ::GetLastError(); // preserve across CloseHandle calls
 target->PostMessage(UPM_FINISHED, (WPARAM)err, (LPARAM)pid);
 delete this;
 return FALSE;
} /* failed to start */

Note that if the process fails to start, the target receives a UPM_FINISHED message, although it will not have received a UPM_PROCESS_HANDLE message. Note that the WPARAM is the error code of why the process creation failed.

target->PostMessage(UPM_PROCESS_HANDLE, (WPARAM)procinfo.hProcess, (LPARAM)pid);

The PostMessage call notifies the target window that the process has started, and passes the process handle in. I don't know what good the process handle will do, but it seemed a reasonable thing to pass in. Note that this is passed in only if the process has started successfully.

// Now close the output pipes so we get true EOF/broken pipe

If the child process terminates, the handle is closed, but what is really closed is the handle in the child process. The hwriteToParent and hwriteToParent2 handles remain valid. Consequently, the ReadFile operation would not receive a broken pipe error, and would hang forever, waiting for some other process which might have an active handle to send it data. By closing our own copies of the handles, this means that the only handles available are those of the child process, and when it terminates, they will be implicitly closed. Since this will mean the last instance of the handle has been closed, the pipe will be broken and ReadFile will receive the correct notification.

 // We have to create a listener thread. We create a worker
 // thread that handles this
 CWinThread * thread = AfxBeginThread(listener, (LPVOID)this);
 if(thread == NULL)
{ /* failed */
     DWORD err = ::GetLastError();
 target->PostMessage(UPM_FINISHED, (WPARAM)err, (LPARAM)pid);
 delete this;
 return FALSE;
} /* failed */

This creates a worker thread to receive the data from the child process. Note that if this cannot be created, no output will arrive at the controlling thread window. Therefore, I send a UPM_FINISHED message to notify the window that the process is effectively terminated. Note that the error code describing the failure mode is passed back.

 return TRUE;
} // Process::run


This is the second-level method (see my technique for thread creation); this is the non-static method which is executed in the context of the thread and the Process object. This reads the input stream, which may contain several lines of text, splits the line up, and sends each line to the parent. 

#define MAX_LINE_LENGTH 1024

void Process::listener()
     TCHAR buffer[MAX_LINE_LENGTH + 1];

     CString * line;
     line = new CString;

     DWORD bytesRead;
     while(::ReadFile(hreadFromChild, buffer, dim(buffer) - 1, &bytesRead, NULL))
	{ /* got data */
	 if(bytesRead == 0)
	    break; // EOF condition
	 buffer[bytesRead] = _T('\0');
	 // Convert to lines
	 LPTSTR b = buffer;
	    { /* convert and send */
	     LPTSTR p = _tcschr(b, _T('\n'));
	     if(p == NULL)
		{ /* incomplete line */
		 *line += b;
		 break; // leave assembly loop
		} /* incomplete line */
		{ /* complete line */
		 int offset = 0;
		 if(p - b > 0)
		    { /* get rid of \r */
		     if(p[-1] == _T('\r'))
			offset = 1;
		    } /* get rid of \r */
		 *line += CString(b, (p - b) - offset);
		 target->PostMessage(UPM_LINE, (WPARAM)line, (LPARAM)pid);
		 b = p + 1;
		 line = new CString;
		} /* complete line */
	    } /* convert and send */
	} /* got data */

     DWORD err = ::GetLastError();


We are now done reading, so we close the handle to clean it up. Otherwise, we would have a lot of handles left around when the program finished.

 if(line->GetLength() > 0)
target->PostMessage(UPM_LINE, (WPARAM)line, (LPARAM)pid);
delete line;

The above lines send any partially-built line that remains to the target window. However, if no content is in the string, the CString object still needs to be deleted.

 DWORD status = 0;
status = err;

A normal EOF or an ERROR_BROKEN_PIPE are valid termination conditions. If it is any other error, something went wrong, so the status is passed back via the UPM_FINISHED message.

target->PostMessage(UPM_FINISHED, status, (LPARAM)pid);

delete this;

This final operation deletes the Process object. Since otherwise there is no good way to track this, I chose to do it here. This means that only a Process * variable can be used to hold a Process object; a Process variable cannot be declared.

} // Process::listener

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


25 Oct 2001 - download file updated.

Send mail to with questions or comments about this article.
Copyright © 1999 All Rights Reserved


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


About the Author

Joseph M. Newcomer
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]

You may also be interested in...


Comments and Discussions

QuestionDeadlock? Pin
masanli26-Nov-07 18:18
membermasanli26-Nov-07 18:18 
AnswerRe: Deadlock? Pin
Joseph M. Newcomer27-Nov-07 6:58
memberJoseph M. Newcomer27-Nov-07 6:58 
GeneralRe: Deadlock? Pin
masanli27-Nov-07 17:34
membermasanli27-Nov-07 17:34 
GeneralRe: Deadlock? Pin
Joseph M. Newcomer27-Nov-07 20:10
memberJoseph M. Newcomer27-Nov-07 20:10 
GeneralRe: Deadlock? Pin
masanli28-Nov-07 19:22
membermasanli28-Nov-07 19:22 
GeneralRe: Deadlock? Pin
Joseph M. Newcomer28-Nov-07 20:12
memberJoseph M. Newcomer28-Nov-07 20:12 
GeneralYour code will not work always Pin
Elmue16-Dec-06 0:56
memberElmue16-Dec-06 0:56 

Starting some Console programs you will not see ANYTHING in the output window while the child process is running. You will see the complete output at once: when the child process has terminated.

If you start the console application in Cmd.exe you see that it permananetly prints output, but you don't see it in the output window of this project before the child process has terminated!


After searching for many hours I found the reason.
But I have bad news for all those who have the same problem:

The cause lies in stupid Windows but in this MFC project you have no chance to solve it. This problem can only be worked around by recompiling the Console application!

If you create a C++ console project and paste this code:

#include <windows.h>
#include <stdio.h>

int main(int argc, char* argv[])
    for (int i=0; i<10000; i++)
        printf ("Hello World!\n");
        Sleep (100);
    return 0;

and then start this Exe it writes and endless list of "Hello World!" to the DOS box. (Cmd.exe)

But if you start the same EXE using this project, you will not see ANYTHING!!

After searching a lot I found the MSDN article

KB 190351: HOWTO: Spawn Console Processes with Redirected Standard Handles

NOTE: Child processes that use such C run-time functions as printf() and fprintf() can behave poorly when redirected. The C run-time functions maintain separate IO buffers. When redirected, these buffers might not be flushed immediately after each IO call. As a result, the output to the redirection pipe of a printf() call or the input from a getch() call is not flushed immediately and delays, sometimes-infinite delays occur. This problem is avoided if the child process flushes the IO buffers after each call to a C run-time IO function. Only the child process can flush its C run-time IO buffers. A process can flush its C run-time IO buffers by calling the fflush() function.

NOTE: Windows 95 and Windows 98 require an extra step when you redirect the standard handles of certain child processes.

After changing the C++ code into

int main(int argc, char* argv[])
     for (int i=0; i<10000; i++)
          fprintf(stdout, "Hello World!\n");
          fflush (stdout);
          Sleep  (100);
     return 0;

it will work!

Another solution would be setting
setvbuf(stdout, 0, _IONBF, 0);

in the main() procedure.

As the majority of console applications is written in C or C++ you will find a lot of programs which can NOT be monitored live with this project!

What a poor operating system: Microsoft Windows!
It cannot even redirect Console output from C++ applications!
Instead of admitting a bug in the MSDN they should have fixed the problem instead!
The knowledge base article is from 2003!
There should be an auto-flush after each line which has been printed, then ALL console applications could be monitored!


-- modified at 6:10 Saturday 16th December, 2006
GeneralDoesn't seem to work with xcopy Pin
pluggy9-Aug-06 11:26
memberpluggy9-Aug-06 11:26 
GeneralShowWindow deadlock Pin
anna_mouse19-Jun-06 7:07
memberanna_mouse19-Jun-06 7:07 
GeneralObservations Pin
Peter Ritchie26-Apr-05 9:38
memberPeter Ritchie26-Apr-05 9:38 
GeneralRe: Observations Pin
Joseph M. Newcomer1-May-05 18:08
memberJoseph M. Newcomer1-May-05 18:08 
Generalfailing threads Pin
Henk van den Toorn15-Dec-04 4:08
memberHenk van den Toorn15-Dec-04 4:08 
GeneralRe: failing threads Pin
Joseph M. Newcomer18-Dec-04 16:16
memberJoseph M. Newcomer18-Dec-04 16:16 
GeneralRe: failing threads Pin
henk van den toorn27-Dec-04 2:03
memberhenk van den toorn27-Dec-04 2:03 
GeneralRe: failing threads Pin
Joseph M. Newcomer27-Dec-04 5:42
memberJoseph M. Newcomer27-Dec-04 5:42 
GeneralHiding the console Pin
ronpeters4-Dec-03 7:38
memberronpeters4-Dec-03 7:38 
GeneralEnter a deadlock and run out the Memory! Pin
Highersong31-Oct-03 22:02
memberHighersong31-Oct-03 22:02 
GeneralRe: Enter a deadlock and run out the Memory! Pin
Highersong31-Oct-03 22:17
memberHighersong31-Oct-03 22:17 
GeneralRe: Enter a deadlock and run out the Memory! Pin
Joseph M. Newcomer31-Oct-03 22:20
memberJoseph M. Newcomer31-Oct-03 22:20 
GeneralPassing instruction to the screen Pin
vgandhi13-Aug-03 14:40
membervgandhi13-Aug-03 14:40 
GeneralRe: Passing instruction to the screen Pin
Joseph M. Newcomer14-Aug-03 5:39
memberJoseph M. Newcomer14-Aug-03 5:39 
GeneralRe: Passing instruction to the screen Pin
vgandhi14-Aug-03 5:59
membervgandhi14-Aug-03 5:59 
GeneralRe: Passing instruction to the screen Pin
Joseph M. Newcomer14-Aug-03 6:52
memberJoseph M. Newcomer14-Aug-03 6:52 
GeneralDelayed Output Problem Pin
gbg1088-Jan-03 9:55
membergbg1088-Jan-03 9:55 
GeneralRe: Delayed Output Problem Pin
Joseph M. Newcomer8-Jan-03 12:48
memberJoseph M. Newcomer8-Jan-03 12:48 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.180111.1 | Last Updated 26 Oct 2001
Article Copyright 2001 by Joseph M. Newcomer
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid