dzumwalt
asked on
WaitForSingleObject not waiting when executing WinWord.
I am using the attached function to execute a command line and to wait until the executed program is done before proceeding forward. When I use it for MS Excel or Adobe PDF reader, it functions as expected. When I try to execute WINWORD and pass as a parameter the path to the document that is to be opened, WaitForSingleObject returns immediately, even before the WORD program shows on the screen.
Normally I delete the temporary file that is being opened by the called process once the process has completed. the fact that WINWORD is returning immediately is causing an obvious problem.
If I place a breakpoint right after WaitForSingleObject is called, Word eventually shows.
Does anybody know why Word is acting this way .... and more importantly does anybody know how I can program around this?
Normally I delete the temporary file that is being opened by the called process once the process has completed. the fact that WINWORD is returning immediately is causing an obvious problem.
If I place a breakpoint right after WaitForSingleObject is called, Word eventually shows.
Does anybody know why Word is acting this way .... and more importantly does anybody know how I can program around this?
Public Sub ExecuteAndWait(cmdline As String, bShowMaximized As Boolean)
Dim NameOfProc As PROCESS_INFORMATION
Dim NameStart As STARTUPINFO
Dim X As Long
' Set up the parameters that will be used for the subsequent calls.
If bShowMaximized = True Then
NameStart.dwFlags = STARTF_USESHOWWINDOW
NameStart.wShowWindow = SW_SHOWMAXIMIZED
End If
NameStart.cb = Len(NameStart)
' Make the API calls.
X = CreateProcessA(0&, cmdline$, 0&, 0&, 1&, NORMAL_PRIORITY_CLASS, 0&, 0&, NameStart, NameOfProc)
X = WaitForSingleObject(NameOfProc.hProcess, INFINITE)
X = CloseHandle(NameOfProc.hProcess)
End Sub
Could you post the command line you are trying to pass to ExecuteAndWait.
I notice you are using the ANSI version of CreateProcess however your passing a long value for lpApplicationName when it should be a VbNullString or ("winword.exe") so could you post the API declerations for CreateProcess as well and I could help
I notice you are using the ANSI version of CreateProcess however your passing a long value for lpApplicationName when it should be a VbNullString or ("winword.exe") so could you post the API declerations for CreateProcess as well and I could help
For the code you are using the command line should look like this
ExecuteAndWait "C:\Program Files\Microsoft Office\Office\winword.exe ""C:\Documents and Settings\Administrator\tes t doc.doc""", True
You need to put your command line into double quotes like displayed above. Also there is a much better way to execute files and documents using ShellExecute API WaitForSingleObject won't allow windows messages to process to your application if you display any type of user interface it won't allow any type of input or repaint and could be a very nasty white mess on the screen until the application your pending has been closed.This is when you should use MsgWaitForMultipleObjects because it will allow you to process window messages to your user interface.
I have written a simple example that can take a file document or execuatable and open it so that your application stays responsive during the process.
ExecuteAndWait "C:\Program Files\Microsoft Office\Office\winword.exe ""C:\Documents and Settings\Administrator\tes
You need to put your command line into double quotes like displayed above. Also there is a much better way to execute files and documents using ShellExecute API WaitForSingleObject won't allow windows messages to process to your application if you display any type of user interface it won't allow any type of input or repaint and could be a very nasty white mess on the screen until the application your pending has been closed.This is when you should use MsgWaitForMultipleObjects because it will allow you to process window messages to your user interface.
I have written a simple example that can take a file document or execuatable and open it so that your application stays responsive during the process.
Option Explicit
Private Const SEE_MASK_NOCLOSEPROCESS As Long = &H40
Private Const WAIT_OBJECT_0 As Long = 0
Private Const INFINITE As Long = &HFFFF
Private Const QS_ALLINPUT& = (&H40 _
Or &H20 _
Or &H10 _
Or &H8 _
Or &H4 _
Or &H2 _
Or &H80 _
Or &H1)
Private Type SHELLEXECUTEINFO
cbSize As Long
fMask As Long
hwnd As Long
lpVerb As String
lpFile As String
lpParameters As String
lpDirectory As String
nShow As Long
hInstApp As Long
lpIDList As Long
lpClass As String
hkeyClass As Long
dwHotKey As Long
hIcon As Long
hProcess As Long
End Type
Private Declare Sub RtlZeroMemory Lib "kernel32.dll" ( _
Destination As Any, _
ByVal Length As Long)
Private Declare Function ShellExecuteEx Lib "shell32.dll" ( _
lpExecInfo As SHELLEXECUTEINFO) As Long
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long) As Long
Private Declare Function MsgWaitForMultipleObjects Lib "user32" ( _
ByVal nCount As Long, _
pHandles As Long, _
ByVal fWaitAll As Long, _
ByVal dwMilliseconds As Long, _
ByVal dwWakeMask As Long) As Long
Private Declare Function PathFileExistsW Lib "shlwapi.dll " ( _
ByVal pszPath As Long) As Boolean
Public Sub ShellExecuteWait(ByVal ProgramPath As String)
Dim ExecuteInfo As SHELLEXECUTEINFO
Dim lBusy As Long
' Check if the file exists
If PathFileExistsW(StrPtr(ProgramPath)) Then
' Initiate struct
RtlZeroMemory ExecuteInfo, Len(ExecuteInfo)
' Setup struct
With ExecuteInfo
.cbSize = Len(ExecuteInfo)
.fMask = SEE_MASK_NOCLOSEPROCESS
.lpFile = ProgramPath
.lpVerb = "open"
.nShow = 1
.lpDirectory = Left$(ProgramPath, InStrRev(ProgramPath, "\") - 1)
End With
' Pass struct info
Call ShellExecuteEx(ExecuteInfo)
' Pend process event signal
Do
lBusy = MsgWaitForMultipleObjects(1, ExecuteInfo.hProcess, False, INFINITE, QS_ALLINPUT)
DoEvents
Loop Until lBusy = WAIT_OBJECT_0
' Close handle
CloseHandle ExecuteInfo.hProcess
'Display some info...
Debug.Print ExecuteInfo.lpFile & " has been closed " & Time()
End If
End Sub
Private Sub Command1_Click()
ShellExecuteWait "C:\Documents and Settings\Administrator\test doc.doc"
End Sub
ASKER
Thanks egl1044 for your input!
While it is much more convenient to have the doevents executing and thus I don't have that white screen you mentioned in my original application, the behavior when executing winword is exactly the same.
As I said before, when I am executing other programs suich as a PDF reader, or Microsoft Excel, MsgWaitForMultipleObjects will cause the process to wait until the program complete. WinWord seems to be different though. It's seems like the EXE I am calling is spawning another executable and then immediately terminating which then causes MsgWaitForMultipleObjects to indicate the process is complete (pure conjecture on my part - I have no data to back that up).
If I place a breakpoint on the line where you are closing the handle, and if I just wait for about 10 seconds, my document appears on the screen in Microsoft Word!
Here is the actual path to the executable I am using:
C:\Program Files\Microsoft Office\OFFICE11\WINWORD.EX E
... and here is the parameter string that I am placing in ExecuteInfo.lpParameters:
C:\DOCUME~1\Dan\LOCALS~1\T emp\TEMP_8 86577\327_ brillvia1- 28final.RT F
Is my assumption above correct? If so, how can handle that?
While it is much more convenient to have the doevents executing and thus I don't have that white screen you mentioned in my original application, the behavior when executing winword is exactly the same.
As I said before, when I am executing other programs suich as a PDF reader, or Microsoft Excel, MsgWaitForMultipleObjects will cause the process to wait until the program complete. WinWord seems to be different though. It's seems like the EXE I am calling is spawning another executable and then immediately terminating which then causes MsgWaitForMultipleObjects to indicate the process is complete (pure conjecture on my part - I have no data to back that up).
If I place a breakpoint on the line where you are closing the handle, and if I just wait for about 10 seconds, my document appears on the screen in Microsoft Word!
Here is the actual path to the executable I am using:
C:\Program Files\Microsoft Office\OFFICE11\WINWORD.EX
... and here is the parameter string that I am placing in ExecuteInfo.lpParameters:
C:\DOCUME~1\Dan\LOCALS~1\T
Is my assumption above correct? If so, how can handle that?
ASKER
Increasing point value of this to 400.
ASKER CERTIFIED SOLUTION
membership
Create a free account to see this answer
Signing up is free and takes 30 seconds. No credit card required.
do you have to use waitforsingleobject?
Dim wshShell
Set wshShell = CreateObject("WScript.Shel l")
wshShell.Run """C:\Program Files\Microsoft Office\Office12\winword.ex e "" ""C:\Documents and Settings\Administrator\tes t doc.doc""", , True
Debug.Print Now
Set wshShell = Nothing
Dim wshShell
Set wshShell = CreateObject("WScript.Shel
wshShell.Run """C:\Program Files\Microsoft Office\Office12\winword.ex
Debug.Print Now
Set wshShell = Nothing
I think I know what is happening.
You already have an instance of WINWORD.EXE open and when you make the call hProcess = 0 so WaitforSingleObject and MsgWaitforMultipleObjects are not waiting on any type of event to occur because the process failed. However if there is no instances open of WINWORD.EXE it works.
To me it seems as if you open word externally from another application or using your mouse and since you have the instance open already your calls fail to obtain the process handle making any type of waiting function fail
A handle to the newly started application. This member is set on return and is always NULL unless fMask is set to SEE_MASK_NOCLOSEPROCESS. Even if fMask is set to SEE_MASK_NOCLOSEPROCESS, hProcess will be NULL if no process was launched. For example, if a document to be launched is a URL and an instance of Internet Explorer is already running, it will display the document. No new process is launched, and hProcess will be NULL.
You already have an instance of WINWORD.EXE open and when you make the call hProcess = 0 so WaitforSingleObject and MsgWaitforMultipleObjects are not waiting on any type of event to occur because the process failed. However if there is no instances open of WINWORD.EXE it works.
To me it seems as if you open word externally from another application or using your mouse and since you have the instance open already your calls fail to obtain the process handle making any type of waiting function fail
A handle to the newly started application. This member is set on return and is always NULL unless fMask is set to SEE_MASK_NOCLOSEPROCESS. Even if fMask is set to SEE_MASK_NOCLOSEPROCESS, hProcess will be NULL if no process was launched. For example, if a document to be launched is a URL and an instance of Internet Explorer is already running, it will display the document. No new process is launched, and hProcess will be NULL.
ASKER
Interesting. So under this scenario I don't have to first determine the program that is associated with the file extension and then call this process. I just pass in the path to the file and leave it to ShellExecuteEx to figure out the proper program to run.
That worked. Good job! Thanks egl1044!
That worked. Good job! Thanks egl1044!
ASKER
Pursuant to your last comment egl1004 I did another test.
I started word with a blank document. I minimized it.
I went to my application and "opened" the rtf.
This caused a second instance of Word to appear in my taskbar. MsgWaitForMultipleObjects continued to report BUSY
I exited the RTF document. MsgWaitForMultipleObjects still reported BUSY. The original instance of Word was still running.
I exited the original instance of Word. MsgWaitForMultipleObjects never released.
Comments? (boosting to 500 points now)
I started word with a blank document. I minimized it.
I went to my application and "opened" the rtf.
This caused a second instance of Word to appear in my taskbar. MsgWaitForMultipleObjects continued to report BUSY
I exited the RTF document. MsgWaitForMultipleObjects still reported BUSY. The original instance of Word was still running.
I exited the original instance of Word. MsgWaitForMultipleObjects never released.
Comments? (boosting to 500 points now)
ASKER
Well .... no reply to the last post by me. Still ... egl1044 helped out a lot and so I will award points to him/her.
Thank you dzumwalt.
Replying to your last comment, This is how the the API is setup to perform and you would have to add additional checksums for sitiuations like these. The problem is that if the application is already running it will not return a process handle so the function fails. If the application is not running it will get the handle and work as intended.
Possible Methods:
1) Enumerate the windows on the system and match a ClassName and alert the user to please close all running instances of said application before running the procedure.( see EnumWindows,GetWindowClass Name)
2) Another option would be to enumerate the process currently running and match the EXE name WINWORD.EXE and then setup your own events by obtaining the real process handle of the already active handle and wait on that(CreateEvent,OpenProce ss) this would take alot of extra work.
3) Simply only using the funtion to execute a process you know the user wouldn't use on a daily basis, adding a reference to a word component so you can automate from your vb application
Replying to your last comment, This is how the the API is setup to perform and you would have to add additional checksums for sitiuations like these. The problem is that if the application is already running it will not return a process handle so the function fails. If the application is not running it will get the handle and work as intended.
Possible Methods:
1) Enumerate the windows on the system and match a ClassName and alert the user to please close all running instances of said application before running the procedure.( see EnumWindows,GetWindowClass
2) Another option would be to enumerate the process currently running and match the EXE name WINWORD.EXE and then setup your own events by obtaining the real process handle of the already active handle and wait on that(CreateEvent,OpenProce
3) Simply only using the funtion to execute a process you know the user wouldn't use on a daily basis, adding a reference to a word component so you can automate from your vb application
Set wshShell = CreateObject("WScript.Shel
wshShell.Run """C:\Program Files\Microsoft Office\Office12\winword.ex
Debug.Print Now
Set wshShell = Nothing