Execute a Program with C++

DanRollins
CERTIFIED EXPERT
Published:
Updated:
This article describes several ways to launch a program using C++, including one that will launch a DOS (console-style) program and capture the console output (the standard output) for programmatic processing.

We are going to focus on the task of launching a program from a Windows GUI application.  For instance, if your program generates a text file, you might want to let your user click a button to view that file in a separate window.  One easy way to do that is to launch Notepad.Exe, passing to it the name of the text file as a command-line parameter.
Launch Notepad to view a text file
Here's a summary of the functions that are available to Windows C++ programmers:

        ShellExecute  
        ShellExecuteEx

        CreateProcess  
        CreateProcessAsUser  
        CreateProcessWithLogonW

        system        
        _exec          
        _spawn, _wspawn  

Let's start with the most popular option:

ShellExecute
This versatile Windows API function is probably your best bet when you want to launch a program.  It lets you control important facets of the program's operation, such as whether its window is to be normal size, minimized, or even hidden at start up.  You can pass any number of parameters -- filenames, switches, and options -- to the program and you can select the default (working) directory for the program.

But the most important feature is that ShellExecute can launch documents.  It is aware of Windows' file type associations and given a verb, such as "open" or "print" and the filename of a document, it will open that document using the appropriate program.  For instance, to display a PDF file, you do not need to know whether to launch Acrobat.Exe or AcroRd32.Exe or where the right executable is stored.  You simply launch the document.  The current user's file-type associations are queried and the right program is started with the right parameters to make it do what's expected.

Launch a Program or Document with ShellExecute
The Microsoft documentation for this API is very good, but I think it will be helpful to recap it and look at a number of examples.  The syntax summary is:

 HINSTANCE ShellExecute(
           HWND     hwnd,  
           LPCTSTR lpOperation,     // Usually "open" or "print" etc.
           LPCTSTR lpFile,              // program or document filename  
           LPCTSTR lpParameters,  // if program, command-line parameters
           LPCTSTR lpDirectory,      // default directory
           INT nShowCmd );          // usually SW_SHOWNORMAL

You can usually set the hwnd parameter to NULL, but you should always specify the lpOperation (verb), lpFile, and nShowCmd.  When launching a program, you will often need to set lpParameters.   The defined "operation verbs" are:

     edit, explore, find, open, print

OPEN is the most-often used verb.  It means "If this is a program, launch it.  If it's a document, figure out which program handles it and launch that program and tell it to open this document." EXPLORE and FIND are shell operations, bringing up an Explorer window or the Search dialog, respectively.  EDIT and PRINT are variations of OPEN that expect the lpFile parameter to be a document.  Not all document types are associated with a program that can EDIT or PRINT, and EDIT often invokes the same action as OPEN.  An "undocumented" verb also exists when the target is an executable:  The RUNAS verb is used to launch programs that need elevated user rights.  There's an example of its usage later in this article.

Examples of ShellExecute:
This function can do a lot of different things.  Here are code examples of some of the many variations.

Launch the Calculator applet
ShellExecute( NULL, "open", "calc.exe", NULL, NULL, SW_SHOWNORMAL );

Open in new window


Launch a program and check for success
int nRet= (int)ShellExecute( 0,"open","calc.exe",0,0,SW_SHOWNORMAL);
                      if ( nRet <= 32 ) {
                              DWORD dw= GetLastError(); 
                              char szMsg[250];
                              FormatMessage(
                                  FORMAT_MESSAGE_FROM_SYSTEM,	
                                  0, dw, 0,
                                  szMsg, sizeof(szMsg),
                                  NULL 
                              );
                              MessageBox( szMsg, "Error launching Calculator" );
                      }

Open in new window


Launch WinRAR to open a ZIP file and take actions...
(extract all .Txt files to a specified directory)
ShellExecute( NULL, "open", 
                          "WinRAR.Exe",        // Program to launch
                          "x c:\\temp\\MyTextArchive.ZIP *.txt c:\\temp\\junk", // parms
                          NULL,                // default dir (don't care)
                          SW_SHOWNORMAL 
                      );

Open in new window


Launch (display) a PDF file
ShellExecute( NULL, "open", 
                          "c:\\temp\\UserGuide.PDF",        // document to launch
                          NULL,       // parms -- not used  when launching a document
                          NULL,       // default dir (don't care here)
                          SW_SHOWNORMAL 
                      );

Open in new window


Print a PDF file
ShellExecute( NULL, "print",        // verb: print
                          "c:\\temp\\UserGuide.PDF",        // document to launch
                          NULL,       // parms -- not used  when launching a document"
                          NULL,       // default dir (don't care here)
                          SW_SHOWNORMAL 
                      );

Open in new window


Run a batch file without showing the DOS box
ShellExecute( NULL, "open",         
                          "c:\\temp\\DoStuff.bat", // program to launch
                          "Hi There",              // parms (will be %1 and %2 in the batch file)
                          "c:\\temp\\junk",        // default dir for the batch
                          SW_HIDE 
                      );

Open in new window


Display a specific folder in the Windows Explorer
(for instance, to allow drag-and-drop into your program)
ShellExecute( NULL, "explore",         
                          "c:\\temp",    // folder name
                          NULL,      
                          NULL,      
                          SW_SHOWNORMAL 
                      );

Open in new window


Launch the default Web Browser, showing specified web page
ShellExecute( NULL, "open",         
                          "http://www.experts-exchange.com/A_1518.html",    // URL
                          NULL,      
                          NULL,      
                          SW_SHOWNORMAL 
                      );

Open in new window


As a preemptive multi-tasking OS, Windows tends to launch programs and then immediately return control to your program (called asynchronous operation).  ShellExecute works like that.  But what if you want to run a program and keep an eye on it?  For instance, if the program does a specific task, say, generate a report, then you will want to know when that program ends so that you can take the next action (say, display or print the report).

You can find out when the launched program ends, but not if you launch it via ShellExecute.   You need an actual process handle (not the phony HINSTANCE that ShellExecute returns).   That brings us to...

ShellExecuteEx
This works just like ShellExecute, except that you need to populate a structure rather than just set function parameters.  It has a couple of other features, including the one we're going to use:  It returns a HANDLE that can be used to see when the Launched program terminates.

SHELLEXECUTEINFO rSEI ={0};
                      rSEI.cbSize=sizeof( rSEI );
                      rSEI.lpVerb= "open";
                      rSEI.lpFile= "calc.Exe";
                      rSEI.lpParameters= 0;
                      rSEI.nShow = SW_NORMAL;
                      rSEI.fMask = SEE_MASK_NOCLOSEPROCESS;
                      
                      ShellExecuteEx( &rSEI );   // you should check for an error here
                      while( TRUE ) {
                          DWORD nStatus= MsgWaitForMultipleObjects(
                                      1, &rSEI.hProcess, FALSE, 
                                      INFINITE, QS_ALLINPUT   // drop through on user activity 
                          );
                          if ( nStatus == WAIT_OBJECT_0 ) {  // done: the program has ended
                              break;
                          }
                          MSG msg;     // else process some messages while waiting...
                          while( PeekMessage(&msg,NULL,0,0,PM_REMOVE) ){
                              DispatchMessage( &msg );
                          }
                      }  // launched process has exited
                      
                      DWORD dwCode;
                      GetExitCodeProcess( rSEI.hProcess, &dwCode );  // ERRORLEVEL value

Open in new window

Something to watch out for when using a message-pumping sequence like that in lines 18-21:  Your U/I is entirely active while waiting for the other program to end.  That means that the user could close your program, which puts you in a sort of limbo.   A better technique is to not sit there in a loop pumping messages, but rather set a timer and use it to check every so often to see if rSEI.hProcess has exited, then start the post-launch processing at that point in time.  Something like this:
void CMyDlg::OnTimer(UINT_PTR nIDEvent)
                      {
                          if ( m_hProcessWeLaunched != 0 ) {
                              if ( WaitForSingleObject( m_hProcessWeLaunched,0)==WAIT_OBJECT_0 ) {
                                  m_hProcessWeLaunched= 0;
                                  MessageBox("done");  // The program ended.  Start the next step.
                              }
                          }
                          CDialog::OnTimer(nIDEvent);
                      }

Open in new window


There is another related scenario that we have not covered.  It's rather uncommon these days, but I'd like to cover it for completeness.   If you launch a console application, it might output a series of lines of text to the screen as it operates.  You can probably capture that output using command-line redirection like so:
#include <process.h>
                      ...
                      system( "dir c:\\temp > c:\\temp\\output.txt" );

Open in new window

That is, let Cmd.Exe redirect the output into a file by using the standard I/O redirection arrow >.  Later, you can process the resulting file (output.txt) as necessary.

The more complex situation is when that console program outputs a continuous stream of text and you'd like to display or otherwise process it in realtime.  To do that, you'll need to set up a pipe and monitor it as it fills up.  All this is leading to the examples of CreateProcess (which, when all is said and done, is where each of these other launching options end up, anyway).

CreateProcess
Again, the MSDN documentation is quite complete, so I won't repeat it here.  For most scenarios, you'll just launch using parameters that are mostly similar to what we've seen with ShellExecute (except that there is no operation verb here -- CreateProcess can only launch programs):
PROCESS_INFORMATION ePI={0};
                      STARTUPINFO         rSI={0};
                      rSI.cb=          sizeof( rSI );
                      rSI.dwFlags=     STARTF_USESHOWWINDOW;
                      rSI.wShowWindow= SW_SHOWNORMAL;  // or SW_HIDE or SW_MINIMIZED
                      
                      BOOL fRet= CreateProcess(
                              "c:\\windows\\notepad.exe",  // program name 
                              " c:\\temp\\report.txt",     // ...and parameters
                              NULL, NULL,  // security stuff (use defaults)
                              TRUE,        // inherit handles (not important here)
                              0,           // don't need to set priority or other flags
                              NULL,        // use default Environment vars
                              NULL,        // don't set current directory
                              &rSI,        // where we set up the ShowWIndow setting
                              &ePI         // gets populated with handle info
                      );      

Open in new window

If it is more convenient, you can set the first parameter to NULL and put the entire command line in the second parameter.  If you do that, and if the program pathname contains any embedded spaces, be sure to enclose that part of the command line in double quotes.

If you need to do a wait-for-process, you'll find the HANDLE of the launched program in the hProcess member of your PROCESS_INFORMATION structure.  You can use it to maintain contact with the program in the same way that we did in the earlier ShellExecuteEx example.

CreateProcess with I/O redirection
In order to capture the standard output of a process in realtime, we need to create pipes and provide them to the CreateProcess function.  Then, while the process is running, we read from the pipe and process the data.  
Capture standard output via a pipeIn the following example, I'm just taking the textual data from the pipe and appending it to the text of an Edit control in the dialog.
int ReadFromPipeNoWait( HANDLE hPipe, char* pDest, int nMax )
                      {
                          DWORD nBytesRead= 0;
                          DWORD nAvailBytes;
                          char cTmp;
                          memset( pDest, 0, nMax );
                          // -- check for something in the pipe
                          PeekNamedPipe( hPipe, &cTmp, 1, NULL, &nAvailBytes, NULL );
                          if ( nAvailBytes == 0 ) {
                               return( nBytesRead );
                          }
                          // OK, something there... read it
                          ReadFile( hPipe, pDest, nMax-1, &nBytesRead, NULL); 
                          return( nBytesRead );
                      }
                      
                      BOOL ExecAndProcessOutput(LPCSTR szCmd, LPCSTR szParms  )
                      {
                          SECURITY_ATTRIBUTES rSA=    {0};
                          rSA.nLength=              sizeof(SECURITY_ATTRIBUTES);
                          rSA.bInheritHandle=       TRUE;
                      
                          HANDLE hReadPipe, hWritePipe;
                          CreatePipe( &hReadPipe, &hWritePipe, &rSA, 25000 );
                      
                          PROCESS_INFORMATION rPI= {0};
                          STARTUPINFO         rSI= {0};
                          rSI.cb=             sizeof(rSI);
                          rSI.dwFlags=     STARTF_USESHOWWINDOW |STARTF_USESTDHANDLES;
                          rSI.wShowWindow= SW_HIDE;  // or SW_SHOWNORMAL or SW_MINIMIZE
                          rSI.hStdOutput=  hWritePipe;
                          rSI.hStdError=   hWritePipe;
                      
                          CString sCmd; sCmd.Format( "\"%s\" %s", (LPCSTR)szCmd, (LPCSTR)szParms );
                      
                          BOOL fRet=CreateProcess(NULL,(LPSTR)(LPCSTR)sCmd, NULL,
                                    NULL,TRUE,0,0,0, &rSI, &rPI );
                          if ( !fRet ) {
                               return( FALSE );
                          }
                         //------------------------- and process its stdout every 100 ms
                         char dest[1000];
                         CString sProgress = "";
                         DWORD dwRetFromWait= WAIT_TIMEOUT;
                         while ( dwRetFromWait != WAIT_OBJECT_0 ) {
                              dwRetFromWait= WaitForSingleObject( rPI.hProcess, 100 );
                              if ( dwRetFromWait == WAIT_ABANDONED ) {  // crash?
                                  break;
                              }
                              //--- else (WAIT_OBJECT_0 or WAIT_TIMEOUT) process the pipe data
                              while ( ReadFromPipeNoWait( hReadPipe, dest, sizeof(dest) ) > 0 ) {
                                  // ------------------ Do something with the output.
                                  // ------------------ Eg, insert at the end of an edit box
                                  int iLen= gpEditBox->GetWindowTextLength();
                                  gpEditBox->SetSel(    iLen, iLen);
                                  gpEditBox->ReplaceSel( dest );
                              }
                          }
                          CloseHandle( hReadPipe  );
                          CloseHandle( hWritePipe );
                          CloseHandle( rPI.hThread); 
                          CloseHandle( rPI.hProcess);
                          // MessageBox("All done!");
                          return TRUE;
                      }

Open in new window

And here's an example of starting that whole sequence:
void CMyTestDlg::OnBnClickedButton1()
                      {
                          CString sCmd=  "c:\\windows\\system32\\Cmd.Exe";
                          CString sParms=" /c dir c:\\windows\\*.* /s"; 
                          gpEditBox= &m_ctlEditBox;
                          gpEditBox->SetLimitText( 500000 );  // plenty of room
                      
                          BOOL fRet= ExecAndProcessOutput( sCmd, sParms );

Open in new window


Variations of CreateProcess
CreateProcessAsUser and CreateProcessWithLogonW are variations of CreateProcess that allow you to run a program as a different user.  For instance, this would let you perform elevated administrative actions... of course you need to know the administrator username and password.

Interestingly, there is an undocumented variation of ShellExecute that provides a similar capability.  If you set the operation verb to "runas" then you can elevate the privilege level under which the process will run.  The shell pops up the Run As dialog box and lets you select a different user and enter the password.  This does not work for "launching documents" -- the command must be an executable file and the filename and/or switches and options must be in the separate parameter:

Launch a program with elevated privileges
ShellExecute( NULL, "runas", 
                          "c:\\windows\\notepad.exe",  // program name 
                          " c:\\temp\\report.txt",     // ...and parameters
                          NULL,       // default dir (don't care here)
                          SW_SHOWNORMAL 
                      );

Open in new window


spawn, _exec, and system
The last three from the list are part of the C-Runtime Library and what might be called legacy functions; they were created back when all programs were console applications.

#include <process.h>
                      ...
                      system("dir c:\\temp");  // any DOS command
                      system("notepad");       // execute as from command line
                      
                      _spawn("dir c:\\temp"); // similar, but there are many options
                      
                      int nRet= _spawnl( _P_WAIT, "c:\\windows\\notepad.exe", "notepad", 0 );
                      
                      int nRet= _execl(  "c:\\windows\\notepad.exe", "notepad", 0 );

Open in new window

The system function always opens a DOS window.  It appears to to run
     Cmd.Exe /c commandline

There are several variations of spawn... options for setting up the environment, searching the command search path and so forth.  Each variation has a slightly different name -- ending with some combination of e, l, v, and/or p and each version handles a variable number of parameters.  There is a similar naming patterns and functionality differences for _exec.   The odd thing about _exec is that it kills the current process as soon as the other one begins running.  It clearly hearkens back to the days of memory-limited single-tasking computers.   I don't recommend spending much time with these last three options.

Summary
We've seen a variety of ways to start a program using C++ in Windows.  If you need to run an external program from your own application, you should first look at ShellExecute.  You can also consider using CreateProcess, and you'll probably need it for advanced scenarios, such as when you need if to manage the redirected standard I/O for the launched program.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author,  please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
9
100,457 Views
DanRollins
CERTIFIED EXPERT

Comments (5)

Commented:
Thanks! I really like your articles! Make a book? "C++ Cooking from Dan Rollins" :)
Please continue.
CERTIFIED EXPERT
Author of the Year 2009

Author

Commented:
Thanks!  This one was tough to write because I had to test so many code fragments, but I ended up learning a lot along the way....
nice and clear article. keep up the good work.

Just a small info:
ShellExecuteEx requires RPC service to be turned on to get the status. It will be nice if you add this also into your article.
Thank you, Dan, for a great and very helpful article.  I'd like to add a snippet showing how to use ShellExecute to start WIn 10 Edge on a page.
ShellExecute( 0, "open", "microsoft-edge:http://www.experts-exchange.com//", NULL, "", SW_SHOWNORMAL)

Open in new window

Thanks for this!  Just exactly what I was looking for.  Very clear and concise!

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.