terminate a process gently

Topic TerminateProcess introduced the equivalent to the Unix "kill -9" command.
The following 4GL procedure KillProcess(pid) also terminates a process, but tries to avoid the use of TerminateProcess.
Procedure CloseProcessWindows is based on API-function EnumWindows. This API-function can not be called from within P4GL because it needs a callback, so I wrote procedure CloseProcessWindows in Pascal and added it to proextra.dll (see page ProExtra.dll). Of course I might as well have included all the rest in Pascal too, but then I would not allow myself to post it on this Progress site :-)
By the way, the topic on CreateProcess shows how to create a process and return a PID.

{windows.i}
{proextra.i}  /* version August 21, 1999 */
 
&GLOBAL-DEFINE PROCESS_QUERY_INFORMATION 1024
&GLOBAL-DEFINE PROCESS_TERMINATE 1
&GLOBAL-DEFINE STILL_ACTIVE 259
 
/* =======================================================
   IsProcessRunning
     Returns TRUE if the process is not terminated. 
     (also returns TRUE if the process is hanging)
   ------------------------------------------------------- */
FUNCTION IsProcessRunning RETURNS LOGICAL (PID AS INTEGER) :
 
  DEFINE VARIABLE IsRunning   AS LOGICAL NO-UNDO INITIAL NO.
  DEFINE VARIABLE hProcess    AS INTEGER NO-UNDO.
  DEFINE VARIABLE ExitCode    AS INTEGER NO-UNDO.
  DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO.
 
  RUN Sleep IN hpApi (0).
   /* Sleep(0) just gives the remainder of this
      thread's time quantum back to the task switcher so the other 
      process gets the opportunity to finish and release itself. */
 
  RUN OpenProcess IN hpApi
                  ( {&PROCESS_QUERY_INFORMATION},
                    0,
                    PID,
                    OUTPUT hProcess).
  IF hProcess NE 0 THEN DO:
     RUN GetExitcodeProcess IN hpApi
                  ( hProcess,
                    OUTPUT ExitCode,
                    OUTPUT ReturnValue).
     IsRunning = (ExitCode={&STILL_ACTIVE}) AND (ReturnValue NE 0).
     RUN CloseHandle IN hpApi(hProcess, OUTPUT ReturnValue).
  END.
  RETURN IsRunning.
END FUNCTION.
 
 
/* =======================================================
   KillProcess
     terminates a process as gently as possible.
     pHow tells you how it is done, for debugging purposes
   ------------------------------------------------------- */
PROCEDURE KillProcess :
   DEFINE INPUT  PARAMETER PID   AS INTEGER NO-UNDO.
   DEFINE OUTPUT PARAMETER pHow  AS CHARACTER    NO-UNDO.
 
   DEFINE VARIABLE cName         AS CHARACTER    NO-UNDO.
   DEFINE VARIABLE ReturnValue   AS INTEGER NO-UNDO.
   DEFINE VARIABLE ProcessHandle AS INTEGER NO-UNDO.
 
   /* first step:  */
   /* ------------ */
   /* verify if the process is really running */
   pHow='not running'.
   IF NOT IsProcessRunning(PID) THEN RETURN.
 
   /* second step: */
   /* ------------ */
   /* does the process have windows?
      If it does, the nicest way to stop the process is 
      send a WM_CLOSE message to each window, as if a human operator 
      pressed the [x]-titlebar button.  */
 
   /* If the process is very young it might not have created a window yet.
      Use WaitForInputIdle to wait until the process has a window and is 
      ready to receive messages. */
 
   pHow='close'.
   RUN OpenProcess IN hpApi({&PROCESS_QUERY_INFORMATION}, 
                            0, 
                            PID, 
                            OUTPUT ProcessHandle).
   IF ProcessHandle NE 0 THEN
      RUN WaitForInputIdle IN hpApi(ProcessHandle,
                                    1000,  /* one second maximum */
                                    OUTPUT ReturnValue).
 
   RUN CloseProcessWindows IN hpExtra (PID, OUTPUT ReturnValue).
   /* ReturnValue=0 if the PID didn't own any windows.
      The windows may be too busy to close immediately. 
      Give them 5 seconds to respond. 
      That's what the Windows Task Manager would also do. */
   IF ReturnValue NE 0 THEN
      RUN WaitForSingleObject IN hpApi (ProcessHandle,
                                        5000, /* five seconds maximum */
                                        OUTPUT ReturnValue).
   RUN CloseHandle IN hpApi(ProcessHandle, OUTPUT ReturnValue).
 
   /* third step: */
   /* ----------- */
   /* If PID is a Progress session it would be nice to execute PROSHUT.
      You would first have to find the user number 
      via the VST _Connect table. And you would have
      to repeat this for every database the process is connected to. */
 
   /* I am not going to do this, but it would have been nice...   */
 
 
   /* last step: */
   /* ---------- */
   /* because everything else failed: TerminateProcess.
      This is similar to "kill -9" in Unix so should be avoided  */
 
   /* Must assume we have sufficient rights for terminating this process. */
   IF NOT IsProcessRunning(PID) THEN RETURN.
   pHow='kill'.
   RUN OpenProcess IN hpApi({&PROCESS_TERMINATE}, 0, PID, OUTPUT ProcessHandle).
   IF ProcessHandle NE 0 THEN DO:
      RUN TerminateProcess IN hpApi(ProcessHandle, 0, OUTPUT ReturnValue).
      RUN CloseHandle IN hpApi(ProcessHandle, OUTPUT ReturnValue).
   END.
 
   /* if everything failed the process will keep running. How could this happen? */
   IF IsProcessRunning(PID) THEN pHow='failed'.
 
END PROCEDURE.