Solved

External Application: Return Codes and Execution

Posted on 1999-01-22
17
513 Views
Last Modified: 2010-05-18
The question is similar to one already posted.  I need to know how I can run an exe file "synchronously" from VB (not return to VB till the new application that I am launching terminates).  In addition, I need to trap the code returned by the application I launched.  I can use Shell to launch the application, but all that gives me is an AppId.  I do not want to do a "busy wait" till the new application exits, and even if I do decide to wait in a loop till the application exits, how do I trap the return code?

Help!!!
0
Comment
Question by:rafay092498
  • 7
  • 7
  • 2
  • +1
17 Comments
 
LVL 15

Expert Comment

by:Tommy Hui
ID: 1470225
The ideal way is to not use the Shell statement. Instead, use the CreateProcess() Windows API. This returns back a HANDLE. You can use this HANDLE for the WaitForSingleObject() API to know when the application has terminated.
0
 

Author Comment

by:rafay092498
ID: 1470226
Agreed.  But how does this get around the problem of trapping the exact code returned by the error (e.g. did the application exit with code 0, 1, 2, other???)?  From what I know (and it is quite little!), the API only indicates what event caused the process to terminate (a timeout, normal termination etc) but the exact return code returned by the application cannot be captured by the calling function.
0
 
LVL 15

Expert Comment

by:ameba
ID: 1470227
  Dim ecode As Long, nret As Long, nret2 As Long
   nret = WaitForSingleObject(hProcess, CLng(TimeOut))
   nret2 = GetExitCodeProcess(hProcess, ecode) ' ecode will contain exitcode
   Call CloseHandle(hProcess)

0
 
LVL 15

Expert Comment

by:ameba
ID: 1470228
Or, get exitcode only when 'Normal completion'
   If nret = 0 Then
      nret2 = GetExitCodeProcess(hProcess, ecode) ' ecode will contain exitcode
    End If
0
 

Author Comment

by:rafay092498
ID: 1470229
For some reason, the CreateProcess is not returning a handle.  Maybe I am making a basic mistake here, but when I execute the following code, lResult contains a zero after CreateProcess call.  Somehow, it seems that there is something wrong here! Any suggestions?
--
Dim uSecurityAttrib As SECURITY_ATTRIBUTES   'passed by ref
Dim uSecurityAttrib2 As SECURITY_ATTRIBUTES
Dim uProcessInfo As PROCESS_INFORMATION
Dim uStartupInfo As STARTUPINFO
Dim lResult As Long, nRet As Long, nret2 As Long, ecode As Long, Timeout As Long
Timeout = 3000000    'set to see if this was causing problems
If Index = 0 Then       'OK
  lResult = CreateProcess("c:\dummyapp.exe", "12", uSecurityAttrib, uSecurityAttrib2, False, 0, "", "", uStartupInfo, uProcessInfo)
   nRet = WaitForSingleObject(lResult, CLng(Timeout))
   nret2 = GetExitCodeProcess(lResult, ecode)
   Call CloseHandle(lResult)
Else                    'Cancel
    Unload Me
End If

0
 

Expert Comment

by:HMWT
ID: 1470230
what are the DECLAREations of WaitForSingleObject and GetExitCodeProcess?
0
 

Expert Comment

by:HMWT
ID: 1470231
what are the DECLAREations of WaitForSingleObject and GetExitCodeProcess?
0
 

Author Comment

by:rafay092498
ID: 1470232
Oops... error...  The calls should have been (I think)...
---
lResult = CreateProcess("c:\dummyapp.exe", "12", uSecurityAttrib, uSecurityAttrib2, False, 0, "", "", uStartupInfo, uProcessInfo) 'retruns a BOOl and NOT a handle!
nRet = WaitForSingleObject(uProcessInfo.hProcess, CLng(Timeout))
nret2 = GetExitCodeProcess(uProcessInfo.hProcess, ecode)
Call CloseHandle(uProcessInfo.hProcess)
---
and the declares are
---
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
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Declare Sub GetStartupInfo Lib "kernel32" Alias "GetStartupInfoA" (lpStartupInfo As STARTUPINFO)
Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long
Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
---

0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 15

Expert Comment

by:ameba
ID: 1470233
thui?
0
 

Author Comment

by:rafay092498
ID: 1470234
so... can u see the problem???  Note that the CreateProcess API never creates the process... what do i do??
0
 
LVL 15

Expert Comment

by:ameba
ID: 1470235
First, create EXE which uses ExitProcess API
and put this in Form_Click
   ExitProcess 5
--
I don't have a problem using it. But, since you didn't reject thui's answer, it means you are expecting solution from him.
0
 

Author Comment

by:rafay092498
ID: 1470236
Well, actually the reason I didnt reject thui's answer was that I was expecting a response from him.  I guess I have waited long enough.  As for your latest suggestion, the problem is not that the new process is NOT returning an exit code.  The problem is that it is never started with the CreateProcess API.  Is there anything wrong with the code that I am using?  Do I need to set anything else? I am a bit new to VB, but in C++, API calls as shown in code above work fine.  Also, what else can I check to make sure that the CreateProcess does actually start the application.  I know for sure that it does not start it because I dont see the new application ask for an input which it should do! Finally, the application that I am going to be calling is not written by me, but i know that it exits with some codes and those codes are what I am trying to capture.  A quick reply (yet again :-)) would be GREATLY appreciated :-)
0
 
LVL 15

Accepted Solution

by:
ameba earned 100 total points
ID: 1470237
' start new project, form1 is created by default
' Add 1 textbox
Option Explicit
Private Const SYNCHRONIZE = &H100000
Private Const WAIT_FAILED = -1&        'Error on call
Private Const WAIT_OBJECT_0 = 0        'Normal completion
Private Const WAIT_ABANDONED = &H80&   '
Private Const WAIT_TIMEOUT = &H102&    'Timeout period elapsed
Private Const INFINITE = -1&           'Infinite timeout
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" (ByVal hProcess As Long, lpExitCode As Long) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long

Private Sub Form_Load()
    Text1.Text = "Notepad c:\config.sys" ' modify this
    Text1.SelLength = 1000
    Caption = "Press Enter to Exit :-)" ' modify this
End Sub

Private Sub Text1_KeyUp(KeyCode As Integer, Shift As Integer)
    If KeyCode = vbKeyReturn Then
        Dim ret As Long
        Screen.MousePointer = vbHourglass
        ret = ShellAndWait(Text1.Text, vbNormalNoFocus)
        'ret = ShellAndWait(Text1.Text, vbNormalFocus, 5000) ' timeout 5 seconds
        Caption = ret
        Screen.MousePointer = vbNormal
    End If
End Sub

Public Function ShellAndWait(ByVal JobToDo As String, Optional ExecMode, Optional TimeOut) As Long
   ' Portions taken from Shell32.Bas, Karl E. Peterson
   ' Shells a new process and waits for it to complete.
   ' Calling application is totally non-responsive while
   ' new process executes.
   '
   Dim ProcessID As Long
   Dim hProcess As Long
   Dim nRet As Long
   Const fdwAccess = SYNCHRONIZE
   
   If IsMissing(ExecMode) Then
      ExecMode = vbMinimizedNoFocus
   Else
      If ExecMode < vbHide Or ExecMode > vbMinimizedNoFocus Then
         ExecMode = vbMinimizedNoFocus
      End If
   End If
   
   On Error Resume Next
   ProcessID = Shell(JobToDo, CLng(ExecMode))
   If Err Then
      ShellAndWait = vbObjectError + Err.Number
      Exit Function
   End If
   On Error GoTo 0
   
   If IsMissing(TimeOut) Then
      TimeOut = INFINITE
   End If
   
   hProcess = OpenProcess(fdwAccess, False, ProcessID)
   nRet = WaitForSingleObject(hProcess, CLng(TimeOut))
   Call CloseHandle(hProcess)
   
   Select Case nRet
   Case WAIT_OBJECT_0
      Debug.Print "Normal completion."
      Dim ecode As Long, nret2 As Long
      nret2 = GetExitCodeProcess(hProcess, ecode)
     
      Debug.Print "Exit code = " & ecode
      ShellAndWait = ecode ' return exitcode
   Case Else
      Select Case nRet
      Case WAIT_TIMEOUT
         Debug.Print "Timed out!"
      Case WAIT_ABANDONED
         Debug.Print "Wait Abandoned!"
      Case WAIT_FAILED
         Debug.Print "Wait Error:"; Err.LastDllError
      End Select
      ShellAndWait = nRet
   End Select
End Function

0
 

Author Comment

by:rafay092498
ID: 1470238
Finally! Thanks :-)

P.S. Any idea why the API calls were not working?

Thanks again!
0
 
LVL 15

Expert Comment

by:ameba
ID: 1470239
I think your call to CreateProces might have incorrect arguments (lpCommandLine = "12"), but I didn't try.
0
 
LVL 15

Expert Comment

by:ameba
ID: 1470240
CORRECTION:
In my answer, in function ShellAndWait, this line:
   Call CloseHandle(hProcess)
should be moved, to be the last line in function, after the End Select statement.
----
This code was tested with a simple VB project, that uses:
   ExitProcess 5
in Sub Form_Click()
It looks like this doesn't work for all the possible cases, but rafay found a fix.

0
 

Author Comment

by:rafay092498
ID: 1470241
One last comment.  In order for the GetExitCodeProcess API to be able to get the return code, the fdwAccess parameter in OpenProcess has to be set to PROCESS_ALL_ACCESS and not to SYNCHRONIZE, and this is equal to &H80000 + &HF0000 + &HFFF.  With this the API correctly returns the return code of the process.
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Introduction In a recent article (http://www.experts-exchange.com/A_7811-A-Better-Concatenate-Function.html) for the Excel community, I showed an improved version of the Excel Concatenate() function.  While writing that article I realized that no o…
Background What I'm presenting in this article is the result of 2 conditions in my work area: We have a SQL Server production environment but no development or test environment; andWe have an MS Access front end using tables in SQL Server but we a…
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…

708 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

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now