Solved

Hook program closing

Posted on 2008-11-03
5
347 Views
Last Modified: 2013-12-04
Hey,

I'm currently writing a program which uses Shell() to run a batch command line job. I have no problem getting a hwnd to the cmd window, but what I need to be able to do is detect when the cmd process ends. Can anyone help?

I'm posting this in areas other than VB6 hoping someone in the c++ section knows how to do this and I can adapt their code.

Thanks.
0
Comment
Question by:Corrup7ioN
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
5 Comments
 
LVL 86

Expert Comment

by:jkr
ID: 22868160
Since 'Shell()' returns the process ID, you can use this one to wait for the task to complete, e.g. (C/C++)
HANDLE hProcess = OpenProcess(SYNCHRONIZE,FALSE,dwID);
 
WaitForSingleObject(hhProcess,INFINITE);
 
CloseHandle(hProcess);

Open in new window

0
 
LVL 86

Accepted Solution

by:
jkr earned 500 total points
ID: 22868251
BTW, see also http://www.thescarms.com/vbasic/wait.aspx ("Wait for a Process to Terminate w/ WaitForSingleObject"):


   Const SYNCHRONIZE = &H100000
   '
   ' Wait forever
   '
   Const INFINITE = &HFFFF
   '
   ' The state of the specified object is signaled
   '
   Const WAIT_OBJECT_0 = 0
   '
   ' The time-out interval elapsed & the objects state is not signaled
   '
   Const WAIT_TIMEOUT = &H102
 
   Private Declare Function OpenProcess Lib "kernel32" ( _
      ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, _
      ByVal dwProcessId As Long) As Long
 
   Private Declare Function WaitForSingleObject Lib "kernel32"
     (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
 
   Private Declare Function CloseHandle Lib "kernel32" ( _
      ByVal hObject As Long) As Long
 
 
-----------------------------------------
 
' The WaitForSingleObject function returns when one of the following occurs:
   ' - The specified object is in the signaled state.
   ' - The time-out interval elapses.
   '
   ' The dwMilliseconds parameter specifies the time-out interval, in milliseconds.
   ' The function returns if the interval elapses, even if the objects state is
   ' nonsignaled. If dwMilliseconds is zero, the function tests the objects state
   ' and returns immediately. If dwMilliseconds is INFINITE, the functions time-out 
   ' interval never elapses.
   '
   ' This example waits an INFINITE amount of time for the process to end. As a
   ' result this process will be frozen until the shelled process terminates. The
   ' down side is that if the shelled process hangs, so will this one.
   '
   ' A better approach is to wait a specific amount of time. Once the time-out
   ' interval expires, test the return value. If it is WAIT_TIMEOUT, the process
   ' is still not signaled. Then you can either wait again or continue with your
   ' processing.
   '
   ' DOS Applications:
   ' Waiting for a DOS application is tricky because the DOS window never goes
   ' away when the application is done. To get around this, prefix the app that
   ' you are shelling to with "command.com /c".
   '
   ' For example: lPid = Shell("command.com /c " & txtApp.Text, vbNormalFocus)
   '
   ' To get the return code from the DOS app, see the attached text file.
   '
   Dim lPid As Long
   Dim lHnd As Long
   Dim lRet As Long
 
   If Trim$(txtApp) = "" Then Exit Sub
 
   lPid = Shell(txtApp.Text, vbNormalFocus)
 
   If lPid <> 0 Then
 
      'Get a handle to the shelled process.
      lHnd = OpenProcess(SYNCHRONIZE, 0, lPid)
      
      'If successful, wait for the application to end and close the handle.
      If lHnd <> 0 Then
         lRet = WaitForSingleObject(lHnd, INFINITE) CloseHandle (lHnd)
      End If
 
      MsgBox "Just terminated.", vbInformation, "Shelled Application"
   End If

Open in new window

0
 
LVL 16

Expert Comment

by:HooKooDooKu
ID: 22868403
The code snippet below is the WaitShell function I wrote for myself.  The note in it make reference to the point jkr's notes make about a DOS window staying open after the BAT has executed.  In my case, I had found the command windows had a checkbox option somewhere that would cause the window to auto close when done.  Of course, the subroutine could be modified to use the option jkr's notes make mention about.

The concept is similar to what the above codes use, execpt that the WaitShell queries the return code from the Shell application (so you can determine the return code from the program if you ran an EXE in the WaitShell) because it loops checking for a return code rather than looking for when all other processes have terminated.
Public Const STILL_ACTIVE = &H103
 
Public Type PROCESS_INFORMATION
        hProcess As Long
        hThread As Long
        dwProcessId As Long
        dwThreadId As Long
End Type
 
Public Type STARTUPINFO
        cb As Long
        lpReserved As String
        lpDesktop As String
        lpTitle As String
        dwX As Long
        dwY As Long
        dwXSize As Long
        dwYSize As Long
        dwXCountChars As Long
        dwYCountChars As Long
        dwFillAttribute As Long
        dwFlags As Long
        wShowWindow As Integer
        cbReserved2 As Integer
        lpReserved2 As Long
        hStdInput As Long
        hStdOutput As Long
        hStdError As Long
End Type
Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Public Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, lpProcessAttributes As SECURITY_ATTRIBUTES, lpThreadAttributes As SECURITY_ATTRIBUTES, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, lpEnvironment As Any, ByVal lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Public Declare Function CreateProcess_API Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, lpProcessAttributes As Any, lpThreadAttributes As Any, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, lpEnvironment As Any, ByVal lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Public Declare Function ResumeThread Lib "kernel32" (ByVal hThread As Long) As Long
Public Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, ByRef lpExitCode As Long) As Long
 
'Run a command line program and stay here until execution of command is complete
' Returns the Exit Code from the call Program
'NOTE: If a DOS or BAT Program is executed and the DOS window remains on the
'      screen after the DOS Program has executed, this Function will remain in
'      its loop until the DOS Window is closed
'      (check the 'Close on Exit' Propert for the DOS Program)
Public Function WaitShell(CmdLine$, Optional WindowStyle As VbAppWinStyle = vbMinimizedFocus) As Long
Const Source As String = "WaitShell"
Dim CommandLine As String       'CmdLine is Placed in a different String so that it is not modified by the CreateProccess Function
Dim SUI As STARTUPINFO
Dim pi As PROCESS_INFORMATION
Dim hProcess As Long
Dim ExitCode As Long
 
    On Error GoTo CatchError
    
    'Initialize Required Variables
    CommandLine = CmdLine$          'Copy it because CreateProcess Function may try to change it (add a Null to the end)
    With SUI
        .cb = Len(SUI)
        .lpReserved = vbNullString
        .lpDesktop = vbNullString
        .lpTitle = vbNullString
        .cbReserved2 = 0
        .lpReserved2 = 0
        .dwFlags = 1
        .wShowWindow = WindowStyle
    End With
    
    'Create the Thread in a Suspended State
    If CreateProcess_API(vbNullString, CommandLine, ByVal 0, ByVal 0, 0, CREATE_SUSPENDED, ByVal 0, vbNullString, SUI, pi) = 0 Then
        'Handle error located in ERR.LastDLLError
    End If
    
    'Remember the Process Handle
    hProcess = pi.hProcess
    If hProcess = 0 Then
        'Handle the error in Err.LastDLLError Source
    End If
    
    'Release the Thread for Execution
    If ResumeThread(pi.hThread) = -1 Then
        'Handle the Err.LastDLLError Here
    End If
    
    'Watch for the Exit Code to indicate Process has finished
    Do
        If GetExitCodeProcess(hProcess, ExitCode) = 0 Then
            Debug.Assert Err.LastDllError <> 0
            'Handle Error in Err.LastDllError Source
        End If
        
        DoEvents
        Sleep 100
    Loop While ExitCode = STILL_ACTIVE
    
    WaitShell = ExitCode
        
    Call CloseHandle(pi.hProcess)
    Call CloseHandle(pi.hThread)
    
    Exit Function
    
CatchError:
    Call CloseHandle(pi.hProcess)
    Call CloseHandle(pi.hThread)
    'Error Handler Here - Such as return a value indicating an error or raise an error to calling function
End Function

Open in new window

0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 22869772
If you are displaying any type of gui such as a form for example you might want to consider pumping messages to your application so it can respond otherwise your form will not respond
Option Explicit
 
Private Const QS_ALLINPUT       As Long = (&H40 Or &H20 Or &H10 Or &H8 Or &H4 Or &H2 Or &H80 Or &H1)
Private Const WAIT_OBJECT_0     As Long = 0
Private Const SYNCHRONIZE       As Long = &H100000
Private Const INFINITE          As Long = &HFFFF
 
Private Declare Function MsgWaitForMultipleObjects Lib "user32.dll" ( _
    ByVal nCount As Long, _
    ByRef pHandles As Long, _
    ByVal fWaitAll As Long, _
    ByVal dwMilliseconds As Long, _
    ByVal dwWakeMask As Long) As Long
 
Private Declare Function OpenProcess Lib "kernel32.dll" ( _
    ByVal dwDesiredAccess As Long, _
    ByVal bInheritHandle As Long, _
    ByVal dwProcessId As Long) As Long
    
Private Declare Function CloseHandle Lib "kernel32.dll" ( _
    ByVal hObject As Long) As Long
    
Public Sub WaitGui(ByVal szPath As String)
    
    Dim hProcess            As Long
    Dim dwPid               As Long
    Dim dwResult            As Long
    
    dwPid = Shell(szPath, vbNormalFocus)
    
    If dwPid <> 0 Then
        hProcess = OpenProcess(SYNCHRONIZE, 0, dwPid)
        If hProcess <> 0 Then
            Do
                dwResult = MsgWaitForMultipleObjects(1, hProcess, 0, INFINITE, QS_ALLINPUT)
                DoEvents
            Loop Until dwResult = WAIT_OBJECT_0
        End If
    End If
    
    Call CloseHandle(hProcess)
 
End Sub

Open in new window

0
 
LVL 3

Author Comment

by:Corrup7ioN
ID: 22875305
Thanks for the help guys.

I used jkr's solution from thescarms, with a slight modification as suggested in the codes comments (see below). I opted for WaitForSingleObject as I don't have a gui, although I'm sure I will need MsgWaitForMultipleObjects in the future, so thank you.
Do
            
    lret = WaitForSingleObject(lHnd, 5000)
    
Loop While lret = WAIT_TIMEOUT
        
CloseHandle (lHnd)
        
If lret = wait_object Then
    'Terminated
Else
    'Some error
End If

Open in new window

0

Featured Post

NFR key for Veeam Agent for Linux

Veeam is happy to provide a free NFR license for one year.  It allows for the non‑production use and valid for five workstations and two servers. Veeam Agent for Linux is a simple backup tool for your Linux installations, both on‑premises and in the public cloud.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

You can of course define an array to hold data that is of a particular type like an array of Strings to hold customer names or an array of Doubles to hold customer sales, but what do you do if you want to coordinate that data? This article describes…
This article describes how to use a set of graphical playing cards to create a Draw Poker game in Excel or VB6.
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.
The viewer will be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.
Suggested Courses
Course of the Month5 days, 3 hours left to enroll

636 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question