28 November 2015

Closing a child process

This is part 3 of a series on launching a Win32 application from GFA-BASIC 32. In the first part I discussed the WinExec based GB32 functions WinExec and Exec. Another WinExec based command is the Shell command spawning COMMAND.COM and is used to execute MSDOS commands, mainly. All WinExec based commands do not return any value identifying the application. The return value of WinExec based processes is not a handle of process ID and the child process is not accessible through program code.

The second part discusses the System command, which does provide meaningful return values. When used as a function, System() returns an Int64 value containing the process-handle and the process ID; the values by which a process is identified. The System command returns these values in explicitly provided by-reference variables. Both values can be used subsequent API functions, from which some accept a process handle and others a process ID.

One of these functions that accepts a process handle is TerminateProcess, which forces a process to exit. Actually this command brings the process to an abrupt and crashing halt, preventing some of the usual cleanup from happening. DLLs, for example, are not notified when TerminateProcess kills one of their clients. Like TerminateThread, TerminateProcess is abrupt, messy, and best avoided whenever possible or to be used as a last resort. We’ll see when and how to use in the code below that provides a GB function to cleanly close a spawned process.

Most documentation emphasizes to let the process come to a natural end. This means that the user quits the application as any other process. When synchronization is important – the application has to wait for the child process to terminate – specify the Wait keyword with the System command. GB32 makes sure the to wait for the child process to end while keeping the user-interface responsive. See the Help file for more details.

But what if you want to close the Win32 child process from your GB program? There is no command and there is also no Windows API function to accomplish this magic. However, there is a documented https://support.microsoft.com/en-us/kb/178893 that provides the solution when you have a process ID or handle. Here is the code translated to GFA-BASIC 32.

Global Const WAIT_OBJECT_0  = 0
Global Const PROCESS_TERMINATE  = 1

OpenW 1
Dim Process As Large
Process = System("Notepad.exe")
Dim hProcess As Long  = LoLarge(Process)
Dim ProcessID As Long = HiLarge(Process)

Do
  Sleep
Until Me Is Nothing

' Close nicely or terminate abruptly
CloseApp32(ProcessID, True)
' When System return processs handle
~CloseHandle(hProcess)

Function CloseApp32(pID%, Optional Force? = False) As Bool
  ' Purpose: (Forced) Shutdown a 32-Bit Process by PID
  ' Return : True success, False (0) failure
  ' See    : https:'support.microsoft.com/en-us/kb/178893

  Local Const TimeOut = 1000 'ms
  Local Handle hProc
  ' Get process handle when termination allowed
  hProc = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, 0,  pID)
  If hProc
    ' Post WM_CLOSE to all windows whose ProcessID == pID
    ~EnumWindows(ProcAddr(CloseApp32Enum), pID)
    ' Give process a moment to release all resources
    If WaitForSingleObject(hProc, TimeOut) == WAIT_OBJECT_0
      CloseApp32 = True  ' Return Succes
    ElseIf Force?        ' Kill it the messy way?
      CloseApp32 = TerminateProcess(hProc, 0)
    EndIf
    ~CloseHandle(hProc)
  EndIf
EndFunc

Function CloseApp32Enum(hwnd As Handle, lParam As Long) As Long Naked
  Local pID As Long
  ~GetWindowThreadProcessId(hwnd, V:pID)
  If pID == lParam _
    PostMessage hwnd, WM_CLOSE, 0, 0
  Return 1
EndFunc

The CloseApp32 function terminates an autonomously running 32-bits application by posting a WM_CLOSE to all the windows owned by the process. The window procedures of the detached application need to destroy resources that may take some time to complete. When the main thread has come to an end, all DLLs and other system resources are released. This also takes time. Therefore the function waits for at most 1000 ms to check if the process has ended. With many running applications the timeout value may be too short.

One other note. If the System function return a process handle the calling application is still responsible for closing it.

No comments:

Post a Comment