execute a program and wait until it has finished

16-bit (there is a 32-bit solution near end of page)

This code was found on PEG, posted by Stuart Butler a while ago. It is 16 bit code and needs to be reworked for 32-bit. It compiles in version 8.2 but does not wait as expected.
The example program runs notepad, waits until the user has closed notepad and then displays the results of the user's work with notepad.

 /* If we were being run persistent we would want to 
  *** be interrupted when waiting for notepad to finish
  *** if we are closed so include an on close event trigger */
 DEFINE VARIABLE lgClosing AS LOGICAL INITIAL FALSE.
 ON CLOSE OF THIS-PROCEDURE
 DO:
    lgClosing = TRUE.
 END.
 
/* Run Notepad and keep a record of its task number */
 DEFINE VARIABLE nTask AS INT.
 RUN WinExec IN hpApi( "Notepad c:\temp\trash.txt", 1, OUTPUT nTask ).
 IF nTask GT 0 AND nTask LT 32 
 THEN DO: 
    MESSAGE "WinExec failed" VIEW-AS ALERT-BOX.
    RETURN.
 END.
 
/* Now wait for notepad to finish - when notepad is closed
 *** the task number will become invalid and Module
 *** API functions will fail - we are using GetModuleFileName. 
 *** The WAIT-FOR will process events and not hog all the CPU
 *** - pity the PAUSE could not be in milliseconds but will have to do
 *** with what's available.  If you use PROCESS EVENTS in some 
 *** other loop construction (with say a timer VBX) it is a good idea to 
 *** preceed it with a call to the WaitMessage() API call as 
 *** looping with PROCESS EVENTS tends to hog the CPU.
 *** You might want to disable your user interface at this point 
 *** though that may not be necessary if you have run this .p
 *** persistently to deal with the windows app "in the background".
 */
 DEFINE VARIABLE szName AS CHARACTER.
 DEFINE VARIABLE szNameLength AS INTEGER.
 REPEAT:
    WAIT-FOR CLOSE OF THIS-PROCEDURE PAUSE 1.
    szName = FILL(" ",256).
    RUN GetModuleFileName{&A} IN hpApi( nTask, 
                                        OUTPUT szName,
                                        LENGTH(szName),
                                        OUTPUT szNameLength).
    /* We don't want to know the ModuleFileName, we only want
     *** to know if the function fails. Fails if result=0.
     *** A failure indicates that the task has ended. */
    IF (szNameLength=0) OR lgClosing THEN
        LEAVE.
 END.
 
/* Notepad should have finished at this point - let's 
 *** see what the user input */
 MESSAGE "Notepad finished" VIEW-AS ALERT-BOX.
 INPUT FROM c:\temp\trash.txt.
 DEFINE VARIABLE szLine AS CHARACTER.
 REPEAT:
     IMPORT UNFORMATTED szLine.
     MESSAGE szLine VIEW-AS ALERT-BOX.
 END. 
 INPUT CLOSE.

Unfortunately, the repeat loop that checks for the existence of the executing task may slow down the computer.

32-bit

In a 32-bit environment you can use the procedure WaitForSingleObject. This is a very efficient procedure. The input parameter for WaitForSingleObject is a handle to an object, in this case a kernel process object. This is not the same as the hInstance as returned by ShellExecute.

To obtain a process handle you seem to have to start the process using either CreateProcess or ShellExecuteEx.
Here is a (simplified) example for CreateProcess. The next page, titled "wait until MS-Word finished printing" uses ShellExecuteEx but not specifically for its process handle...

  {windows.i}
  DEFINE VARIABLE hProcess AS INTEGER NO-UNDO. 
  DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO.
  hProcess = CreateProcess("notepad.exe c:\config.sys",
                           "",
                           1). 
  IF hProcess=0 THEN
     ShowLastError().
  ELSE DO:
     RUN WaitForSingleObject IN hpApi (hProcess, 
                                       -1,   /* -1=INFINITE */
                                       OUTPUT ReturnValue).
     RUN CloseHandle IN hpApi (hProcess, OUTPUT ReturnValue).
  END

CreateProcess and ShowLastError are declared in windows.i.

Don't forget CloseHandle!

The process object has an internal reference counter. There are/were at least two references: one from within notepad.exe itself, the other one from your program (obtained by CreateProcess). When notepad.exe terminates, it will decrease the object's reference counter but your program still holds one. The object can not be destroyed until the reference counter decreases to zero, so you must use CloseHandle (which decrements the counter by one and invalidates the handle).

In other words: there is a memory leak if you don't call CloseHandle (although the kernel calls it when the Progress session terminates).
CloseHandle does not destroy the object (yet), it just tells the Kernel that it can destroy the object whenever it wants to (as far as you are concerned). CloseHandle is important for every Kernel object: CreateProcess returns a process object and also a thread object. The thread object is already closed inside the P4GL implementation of function CreateProcess in winfunc.p (version May 9, 1998).