Author Topic: How to wait for a background process to exit...  (Read 119 times)

snowsnowsnow

  • Sr. Member
  • ****
  • Posts: 302
How to wait for a background process to exit...
« on: February 20, 2020, 12:40:41 am »
My situation is that I would like to launch a background process using RunShell(), with @GETPROCID.  I then want to do something with that process ID, then I want to sit back and wait for the background process to exit.  Effectively, I want to go back and pretend that I had used RunWait() (or similar, e.g., RunShell() with @WAIT) in the first place.

So, in code, it is like:

procid = RunShell("Something","some args","",@NORMAL,@GETPROCID)
; do something with procid
WaitFor_procid_to_finish()

Now, there is a method for this - that I've used previously - for doing this.  You use some DllCall() to "open" a process, then you use that value to call "GetExitCodeProcess" and then you check that value for the magic value 259.  If it is 259, then it is still running and you have to sleep and then loop and check it again.  When it is something other than 259, then you know the process has finished and the return value is the "exit code" of the process.

Besides being ugly, this method raises the obvious question: What if a process happens to exit with an exit code of 259?  How would you disambiguate this from the case of it still being running?

Anyway, my question is: Is there a better way?  Is there some way to "hook into" whatever functionality RunWait() uses to wait for the process to exit?

Another question; How much of RunWait() is implemented in WB itself, vs. being just a thin wrapper around existing Windows functionality?

ChuckC

  • Full Member
  • ***
  • Posts: 192
Re: How to wait for a background process to exit...
« Reply #1 on: February 20, 2020, 06:07:15 am »
If you have the PID from the child process that you created, then do a DllCall() on the OpenProcess() Win32 API to get a handle to the process.  Be careful that the child process doesn't terminate and then another process gets created with the same PID value between when you create the child process and when you attempt to get a handle to it.

Once you have the handle to the process, make use of DllCall() to call one of the "Wait*()" Win32 API functions, such as WaitForSingleObject().  API Documentation:

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject

A process handle gets "signaled" when the process terminates.  When a process handle is in a signaled state, it will satisfy a wait operation with a result of WAIT_OBJECT_0 [0x00000000].  Once you are finished waiting for the process to terminate, be sure to call CloseHandle() via DllCall() to close the handle, which is necessary to allow the O.S. to clean up the "zombie" process that is lingering due to the open handle(s) that refer to it.

Note that you can specify timeout values for WaitForSingleObject(), so you can control whether the function returns results immediately by simply testing the signaled state of the handle, or waits up until the timeout period has elapsed before returning the results.  If an infinite timeout is specified, then it waits indefinitely for the process handle to enter a signaled state.  A modest timeout used in combination within a loop that also tests for script termination/cancellation requests, too, with proper cleanup being performed.

Once the process handle has been signaled and you know that the process has terminated, you can then call GetExitCodeProcess() using the handle to obtain the exit code that the process' main() function [or equivalent] returned at termination time.

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess

All of this can be accomplished via the .Net Framework, too, if you want to go in that direction instead of making bare-bones API calls via DllCall().

snowsnowsnow

  • Sr. Member
  • ****
  • Posts: 302
Re: How to wait for a background process to exit...
« Reply #2 on: February 20, 2020, 06:49:21 am »
TYVM!  That sounds like what I want.  It may be a while until I get around to testing it, but I'll let you know how it works out.

td

  • Tech Support
  • *****
  • Posts: 3210
    • WinBatch
Re: How to wait for a background process to exit...
« Reply #3 on: February 20, 2020, 10:19:53 am »
Amazingly (amazing because I am lucky to remember what I did yestarday), this topic reminded me of a very similar topic that occurred almost exactly 7 years ago on the old WinBatch WebBoard forum.  If memory servers, snow++ may have even participated in the original thread.   Anyway, here is an old bit of code that I posted as part of that original topic.  Use with care as it like could use some revision and I did not restest it.

Code: Winbatch
;; Create a test process
Pid = RunShell( "notepad.exe", "", "", @NORMAL, @GETPROCID)

; Boilerplate
nExitCode = 2147483647 ; Whatever
hKernel32  = DllLoad("kernel32")
If hKernel32 == "" Then Goto cleanup
hUser32    = DllLoad("User32")
If hUser32 == "" Then Goto cleanup
hExitCode  = BinaryAlloc(4)
If !hExitCode Then Goto cleanup
hhWaitProcess = BinaryAlloc(4)
If !hhWaitProcess Then Goto cleanup

; Need both query(1024) and sync(1048576) access
hWaitProcess = DllCall(hKernel32,long:"OpenProcess",long:1024|1048576,long:0,long:Pid)
If !hWaitProcess Then Goto cleanup
BinaryPoke4(hhWaitProcess, 0, hWaitProcess)

; Suspend until process exits
nResult = 0
While 1

   ; Wake up on any input event in the targeted process  (511 = any input)
   nResult = DllCall(hUser32 ,long:"MsgWaitForMultipleObjects", long:1, lpbinary:hhWaitProcess, long:0, long:-1, long:511)
   If nResult == -1  Then Break

   ; Check for process signaled (0 return means process signaled)
   If DllCall(hKernel32 ,long:"WaitForSingleObject", long:hWaitProcess, long:0) == 0 Then Break

   Yield() ; Give up the rest of a CPU slice to be on the safe side
EndWhile

; Check for sync errors
If nResult == -1 Then Message( "Error", "System error = ":DllLastError())
Then Goto cleanup

; Get the process exit code
DllCall(hKernel32,long:"GetExitCodeProcess",long:hWaitProcess,lpbinary:hExitCode)
nExitCode = BinaryPeek4(hExitCode,0)
:cleanup ; Tidy things up.

if IsDefined(hWaitProcess) then if hWaitProcess then DllCall(hKernel32 ,long:"CloseHandle", long:hWaitProcess)
if IsDefined(hhWaitProcess) then if hhWaitProcess then BinaryFree(hhWaitProcess)
if IsDefined(hExitCode) then if hExitCode  then BinaryFree(hExitCode)
if IsDefined(hUser32) then if hUser32!="" then DllFree(hUser32)
if IsDefined(hKernel32) then if hKernel32!="" then DllFree(hKernel32)

Message("Process terminated", "With this exit code ":nExitCode)

exit
 

"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade