Problems after calling WaitForSingleObject or Sleep API functions

I am using Shell in a VB6 program to launch an external application, then using OpenProcess and WaitForSingleObject API calls to suspend my program until the external application terminates.

I am also using the Sleep API call to suspend my program for specific amounts of time.

My problem is that when my program is suspended in these ways, launching applications "by association" (eg. double-clicking a .doc file) becomes ENORMOUSLY slow. So slow, that the PC appears to lock up.

I see this problem when the application I launched with Shell tries to launch further applications to display .doc files etc, and also in Windows Explorer when I double-click a .doc file.

Does anyone know why this happens, and/or how to fix it?
GasarakiAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

nutwissCommented:
what OS are you using?
0
mcriderCommented:
Try this:

If you want your program to "Freeze" until the shelled program is completed, you can add this code to a MODULE in your program.  It does not require putting your application to sleep:

'-----------------------------------------------------------
Public Const INFINITE = -1&

Private Declare Function OpenProcess Lib "kernel32" (ByVal _
    dwDesiredAccess As Long, ByVal bInheritHandle As Long, _
    ByVal dwProcessID As Long) As Long

   Private 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

   Private Type PROCESS_INFORMATION
      hProcess As Long
      hThread As Long
      dwProcessID As Long
      dwThreadID As Long
   End Type

   Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal _
      hHandle As Long, ByVal dwMilliseconds As Long) As Long

   Private Declare Function CreateProcessA Lib "kernel32" (ByVal _
      lpApplicationName As Long, ByVal lpCommandLine As String, ByVal _
      lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
      ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
      ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
      lpStartupInfo As STARTUPINFO, lpProcessInformation As _
      PROCESS_INFORMATION) 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 Const NORMAL_PRIORITY_CLASS = &H20&
   
  Public Function ExecCmd(cmdline$)
      Dim proc As PROCESS_INFORMATION
      Dim start As STARTUPINFO

      ' Initialize the STARTUPINFO structure:
      start.cb = Len(start)

      ' Start the shelled application:
      ret& = CreateProcessA(0&, cmdline$, 0&, 0&, 1&, _
         NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)


      ' Wait for the shelled application to finish:
         ret& = WaitForSingleObject(proc.hProcess, INFINITE)
         Call GetExitCodeProcess(proc.hProcess, ret&)
         Call CloseHandle(proc.hThread)
         Call CloseHandle(proc.hProcess)
         ExecCmd = ret&
    End Function
'-----------------------------------------------------------

You can then use the following anywhere in your program:

   ExecCmd("C:\WINDOWS\CALC.EXE")

instead of:

   Shell("C:\WINDOWS\CALC.EXE",1)




Cheers!
0
GasarakiAuthor Commented:
nutwiss: I'm using Windows98

mcrider: Thanks, but I already tried using the CreateProcessA API function instead of VB's Shell. The same thing happened when I tried it.

But just to be sure, I pasted your code into a new project - it had only one button which launched notepad.exe using your ExecCmd function. I got the same problem - when I double-clicked a .doc file in Windows Explorer there was an enormous delay before Microsoft Word started and displayed the document (several minutes sometimes).
0
The Ultimate Tool Kit for Technolgy Solution Provi

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy for valuable how-to assets including sample agreements, checklists, flowcharts, and more!

GasarakiAuthor Commented:
Points increased to 400
0
Joe_GriffithCommented:
I have seen something similar happen if you are running Mcafee viruscan.  If you are try turning it off at least temporarily and see if the problem goes away.
0
GasarakiAuthor Commented:
Joe: I am not running McAfee.

And I have tried running a simple test program on a "clean" Windows98 installation that has very little other than Microsoft Office and installed. No AV software is running. Same thing happens.

My test program launches Notepad, and uses WaitForSingleObject to "freeze" until Notepad terminates.

While my program waits for notepad to terminate, I go to "My Computer", and double-click on a .doc file. The first time, Microsoft Word might (!) launch immediately... but if I then close Word, any subsequent attempts to double-click the .doc file gives me the slowdown problem.

ie. using WaitForSingleObject seems to cause a SYSTEM-WIDE problem!

Closing Notepad, or killing my program via ctrl-alt-del, restores immediate launching of Word when I double-click a .doc file.


0
GasarakiAuthor Commented:
Joe: I am not running McAfee.

And I have tried running a simple test program on a "clean" Windows98 installation that has very little other than Microsoft Office and installed. No AV software is running. Same thing happens.

My test program launches Notepad, and uses WaitForSingleObject to "freeze" until Notepad terminates.

While my program waits for notepad to terminate, I go to "My Computer", and double-click on a .doc file. The first time, Microsoft Word might (!) launch immediately... but if I then close Word, any subsequent attempts to double-click the .doc file gives me the slowdown problem.

ie. using WaitForSingleObject seems to cause a SYSTEM-WIDE problem!

Closing Notepad, or killing my program via ctrl-alt-del, restores immediate launching of Word when I double-click a .doc file.


0
mcriderCommented:
OK... I was able to duplicate your problem... Try this code instead:

Add the following to a module:

'-------------------------------------------------
    Declare Function GetExitCodeProcess Lib "kernel32" _
        (ByVal hProcess As Long, lpExitCode As Long) As Long
   
    Declare Function OpenProcess Lib "kernel32" _
        (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, _
        ByVal dwProcessId As Long) As Long
   
    Declare Function CloseHandle Lib "kernel32" _
        (ByVal hObject As Long) As Long
   
    Declare Function SysSetFocus Lib "user32" Alias "SetFocus" _
        (ByVal hwnd As Long) As Long
    Function IsActive(hprog) As Long
        Dim hProc, RetVal As Long
        hProc = OpenProcess(0, False, hprog)
        If hProc <> 0 Then GetExitCodeProcess hProc, RetVal
        IsActive = (RetVal = 259)
        CloseHandle hProc
    End Function
    Function WaitForShell(CmdString As String)
        Dim ProgHandle As Long
        ProgHandle = Shell(CmdString, vbNormalFocus)
        Do
            If Not IsActive(ProgHandle) Then Exit Do
            DoEvents
        Loop
    End Function
'-------------------------------------------------

Then Do this:


Form1.Enabled=False
WaitForShell "C:\WINDOWS\NOTEPAD.EXE"
Form1.Enabled=True
MsgBox "TERMINATED"


You should be able to launch a linked document...


Cheers!
0
Erick37Commented:
Here's why it did not work:

"You have to be careful when using the wait functions and DDE. If a thread creates any windows, it must process messages. DDE sends messages to all windows in the system. If you have a thread that uses a wait function with no time-out interval, the system will deadlock. Therefore, if you have a thread that creates windows, use MsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than WaitForSingleObject."

Another fix:

lPid = Shell("c:\windows\calc.exe", 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
        Enabled = False
        Do
            lRet = WaitForSingleObject(lHnd, 10)
            DoEvents
        Loop While lRet = WAIT_TIMEOUT
        Call CloseHandle(lHnd)
        Beep
        Enabled = True
    End If
End If
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
nutwissCommented:
sod it, have a happy xmas!
0
GasarakiAuthor Commented:
erick37: Do you have a code example using MsgWaitForMultipleObjects? I prefer not to use WaitForSingleObject in a loop. (If I must use a loop I will use the GetExitCodeProcess technique).

mcrider: yes, I know the GetExitCodeProcess technique, and I am going to use that approach if all else fails. I'll give you the points anyway, if I end up doing that. But for now I'll hold out in case I can use MsgWaitForMultipleObjects.
0
Erick37Commented:
This seems to work fine:

Option Explicit
Private Const SYNCHRONIZE = &H100000
Private Const INFINITE = (-1)
Private Const WAIT_OBJECT_0 = 0
Private Const WAIT_TIMEOUT = &H102

Private Sub Command1_Click()
Dim lRet As Long, lPid As Long, lHnd As Long
lPid = Shell("c:\windows\calc.exe", vbNormalFocus)
If lPid <> 0 Then
    'Get a handle to the shelled process.
    lHnd = OpenProcess(SYNCHRONIZE, 0, lPid)
    'Wait for the application to end and close the handle.
    If lHnd <> 0 Then
        Enabled = False
        Do
            lRet = MsgWaitForMultipleObjects(1, lHnd, False, INFINITE, QS_SENDMESSAGE)
            DoEvents
        Loop While lRet <> WAIT_OBJECT_0
        Call CloseHandle(lHnd)
        Beep
        Enabled = True
    End If
End If
End Sub
0
GasarakiAuthor Commented:
Ugh. So, is there no way to enter a CPU efficient "wait" state AND avoid the launch-by-association problem? ie. a technique that does not rely on loops and DoEvents?

It seems not, and I've spent enough time on this problem. After due consideration I think the WaitForSingleObject (with a timeout) running in a loop is the best compromise.

Erick37: Since you suggested it, I'll give you the points - please reply as an "answer"
0
mcriderCommented:
So you're not going to use the GetExitCodeProcess technique?

Cheers!
0
GasarakiAuthor Commented:
mcrider: no, sorry I'm not going to use the GetExitProcess technique after all, though I DO appreciate your efforts to help.

I think using WaitForSingleObject in a loop will be more CPU efficient. A timeout of (say) 30 milliseconds for the WaitForSingleObject call will put my program to sleep most of the time that my shelled app is running... and yet will not give a noticeable delay when launching other apps "by association".

It seems the best compromise...
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic Classic

From novice to tech pro — start learning today.