Solved

External Application: Return Codes and Execution

Posted on 1999-01-22
17
561 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
[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
  • 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
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
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
 
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

MS Dynamics Made Instantly Simpler

Make Your Microsoft Dynamics Investment Count  & Drastically Decrease Training Time by Providing Intuitive Step-By-Step WalkThru Tutorials.

Question has a verified solution.

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

Most everyone who has done any programming in VB6 knows that you can do something in code like Debug.Print MyVar and that when the program runs from the IDE, the value of MyVar will be displayed in the Immediate Window. Less well known is Debug.Asse…
When designing a form there are several BorderStyles to choose from, all of which can be classified as either 'Fixed' or 'Sizable' and I'd guess that 'Fixed Single' or one of the other fixed types is the most popular choice. I assume it's the most p…
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…
This lesson covers basic error handling code in Microsoft Excel using VBA. This is the first lesson in a 3-part series that uses code to loop through an Excel spreadsheet in VBA and then fix errors, taking advantage of error handling code. This l…
Suggested Courses
Course of the Month11 days, 15 hours left to enroll

623 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