Solved

events and timeouts in VB6

Posted on 2008-06-24
12
1,210 Views
Last Modified: 2013-11-25
I'm currently adapting a VB6 app, that runs as a DLL without forms.

The functionality involves waiting by a named pipe to a print driver (Black Ice), and currently uses waitforsingleobject to wait for the the pipe to return a result, or else times out.

However, in the latest print driver, this method has changed to using form based events i.e. the app waits for an 'end document' event to fire.

What I need to do is wait for this event to fire, but if it doesn't I need to specify a timeout. Sleep() hangs the process.

-Is it possible to use waitforsingleobject to wait for an VB6 object's event to fire e.g.

waitforsingleobject(driver_endprinting, 30000)... and if so what is the syntax.

-If not, is there another way of doing it? I've had no success using createwaitabletimer
0
Comment
Question by:tinopener
  • 6
  • 6
12 Comments
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
You will need to use  MsgWaitForMultipleObjects() rather than WaitForSingleObject(). Why? Well the answere is WaitForMultipleObjects() allows you to pump messages to the queue allowing your form to be in a "responsive state" which then will allow your form to recieve the so called "end_document" event.
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
A very simple example of this would be along the lines of the below snippet. Nothing really needs to be changed in your current code. Just replace your (WariForSingleObject) with the new code below.
Option Explicit
 

Private Const WAIT_OBJECT_0             As Long = 0

Private Const QS_ALLINPUT& = (&H40 _

    Or &H20 _

    Or &H10 _

    Or &H8 _

    Or &H4 _

    Or &H2 _

    Or &H80 _

    Or &H1)

    

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
 
 

' Your wait setup would be like along these lines.
 

Dim dwWait As Long

    

    Do

        dwWait = MsgWaitForMultipleObjects(1, EventToWaitForHere, False, TimeOutValueHere, QS_ALLINPUT)

    DoEvents

    Loop Until dwWait = WAIT_OBJECT_0

Open in new window

0
 

Author Comment

by:tinopener
Comment Utility
Thank's for the swift reply ;-)

How do I add the event to the the parameters of MsgWaitForMultipleObjects (or even WaitForSingleObject) i.e. the 'EventToWaitForHere' example in your snippet?

I know it these functions take handle of type long, but I can't see how to associate a handle to the event, which is described in the object browser as so:

Event EndDoc(GroupFileName As String)
    Member of BIPRNDRVLib.BiPrnDrv

Although this is improbable, this illustrates what I'm trying to do:

Dim handle as long
handle = BiPrnDrv.EndDoc
...MsgWaitForMultipleObjects(1, handle..............


0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
I could have misunderstood your question now that I look at it. You had to previously use WaitForSingleObject to wait on the handle? But with the recent changes you no longer need to use any type of wait function because it now has an exposed event that you use in the form when you add a reference?
0
 

Author Comment

by:tinopener
Comment Utility
I was previously using WaitForSingleObject to wait on a named pipe as this is the way the old printer API did it.

The new printer API has exposed events that work fine,  but I need to specify a timeout if the event *doesn't* fire, and I thought that a wait function might be a way of doing this. I'm open to any other suggestions of how to do this.

The routine is something like this:

-start printing
-start capture that waits for 'end document' event
-if 'end document' event doesn't fire after 30 seconds, return failure.

The printer driver reference with the events is an .ocx conrol that needs to sit in a form, although my legacy application is not a graphical app. I am adding a form that I will probably hide just so I can use this control.
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
Okay I think I understand what you need, also before using my code snippet is there any more information about the control itself? Alot of these controls have a Change State so you can tell at which point the status of the job is at and use that to determine why the event didn't fire. Otherwise I wrote a simple wait procedure that should help. You should prob make a global variable before calling the print job set it to (False) then in the end_doc event make the global variable = (True) then in the PTIMERAPCROUTINE routine check if that variable is True or False and act accordingly.
'// Module.bas

Option Explicit
 

Private Type LARGE_INTEGER

    Hi          As Long

    Lo          As Long

End Type
 

Private Const WAIT_OBJECT_0     As Long = 0

Private Const INFINITE          As Long = &HFFFF

Private Const QS_ALLINPUT       As Long = (&H40 _

                                        Or &H20 _

                                        Or &H10 _

                                        Or &H8 _

                                        Or &H4 _

                                        Or &H2 _

                                        Or &H80 _

                                        Or &H1)
 

Private Declare Sub m_mem Lib "kernel32.dll" Alias "RtlMoveMemory" ( _

    Destination As Any, _

    Source As Any, _

    ByVal Length As Long)
 

Private Declare Function CreateWaitableTimerA Lib "kernel32" ( _

    ByVal lpSemaphoreAttributes As Long, _

    ByVal bManualReset As Long, _

    ByVal lpName As String) As Long

    

Private Declare Function SetWaitableTimer Lib "kernel32" ( _

    ByVal hTimer As Long, _

    lpDueTime As LARGE_INTEGER, _

    ByVal lPeriod As Long, _

    ByVal pfnCompletionRoutine As Long, _

    ByVal lpArgToCompletionRoutine As Long, _

    ByVal fResume 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 SleepEx Lib "kernel32" ( _

    ByVal dwMilliseconds As Long, _

    ByVal bAlertable As Long) As Long
 

Private Declare Function CloseHandle Lib "kernel32" ( _

    ByVal hObject As Long) As Long
 

Public Sub CreateTimerObjectAndWait(ByVal lngSeconds As Long)
 

    Dim hTimer          As Long

    Dim dwWait          As Long

    Dim c_int64         As Currency

    Dim l_int64         As LARGE_INTEGER

    

    hTimer = CreateWaitableTimerA(0, 0, "PRINTWAIT")

    c_int64 = (-lngSeconds * 10000000)

    c_int64 = (c_int64 / 10000)

    m_mem l_int64, c_int64, Len(l_int64)

    If SetWaitableTimer(hTimer, l_int64, 0, AddressOf PTIMERAPCROUTINE, 0, 0) Then

        Do

          dwWait = MsgWaitForMultipleObjects(1, hTimer, 0, INFINITE, QS_ALLINPUT)

          DoEvents

        Loop Until dwWait = WAIT_OBJECT_0

        SleepEx 0, 1

    End If

    CloseHandle hTimer

End Sub
 

Public Sub PTIMERAPCROUTINE(ByVal lpArgToCompletionRoutine As Long, ByVal dwTimerLowValue As Long, ByVal dwTimerHighValue As Long)
 

'// Check here if event occured before we hit this routine.

    Debug.Print "if (end_doc)event fired before this then (OK) otherwise (end_doc) wasn't raised"

        

End Sub

Open in new window

0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 

Author Comment

by:tinopener
Comment Utility
The control is very basic and doesn't seem to have a change state like you ask.

That code snippet looks like what I have in mind, and shall give it a go and let you know how I get on. Thanks again.
0
 

Author Comment

by:tinopener
Comment Utility
Yes, that does the trick i.e. when it times out I can work out whether the event fired or not.

However, the CreateTimerObjectAndWait hangs the main thread until it times out, whether the event fires or not. It would be good if when the event fires it stops the timer, so the whole program can terminate immediately. This is the way the old waitforsingleobject method worked.

Is there an obvious way to stop the timer when the event fires?

0
 
LVL 29

Accepted Solution

by:
nffvrxqgrcfqvvc earned 125 total points
Comment Utility
You can create a timer queue and use SetEvent to change the state of the event to a signalled state. I wrote a simple code snippet, Just call ExitTimerQ in your End_Doc event.
Option Explicit
 

Private Const MWMO_ALERTABLE    As Long = &H2

Private Const WAIT_OBJECT_0     As Long = 0

Private Const INFINITE          As Long = &HFFFF

Private Const QS_ALLINPUT       As Long = (&H40 Or &H20 Or &H10 Or &H8 Or &H4 Or &H2 Or &H80 Or &H1)
 

Private Declare Function CreateEventA Lib "kernel32" ( _

    ByVal lpEventAttributes As String, _

    ByVal bManualReset As Long, _

    ByVal bInitialState As Long, _

    ByVal lpName As String) As Long
 

Private Declare Function CreateTimerQueueTimer Lib "kernel32.dll" ( _

    phNewTimer As Long, _

    ByVal TimerQueue As Long, _

    ByVal Callback As Long, _

    ByVal Parameter As Long, _

    ByVal DueTime As Long, _

    ByVal Period As Long, _

    ByVal Flags As Long) As Long
 

Private Declare Function MsgWaitForMultipleObjectsEx Lib "user32" ( _

    ByVal nCount As Long, _

    pHandles As Long, _

    ByVal dwMilliseconds As Long, _

    ByVal dwWakeMask As Long, _

    ByVal dwFlags As Long) As Long
 

Private Declare Function DeleteTimerQueueTimer Lib "kernel32.dll" ( _

    ByVal TimerQueue As Long, _

    ByVal Timer As Long, _

    ByVal CompletionEvent As Long) As Long

    

Private Declare Function SetEvent Lib "kernel32" ( _

    ByVal hEvent As Long) As Long
 

Private Declare Function CloseHandle Lib "kernel32" ( _

    ByVal hObject As Long) As Long
 

Private Declare Function CreateTimerQueue Lib "kernel32.dll" () As Long
 

    Dim hTimer          As Long

    Dim hTimerQueue     As Long

    Dim gDoneEvent      As Long

    Dim dwWait          As Long
 

Public Sub SetTimerQ(ByVal dwMilliseconds As Long)
 

    gDoneEvent = CreateEventA(vbNullString, 1, 0, "PRINTERWAIT")

    hTimerQueue = CreateTimerQueue

    Call CreateTimerQueueTimer(hTimer, hTimerQueue, AddressOf TimerRoutine, 0, dwMilliseconds, 0, 0)

    '// TODO: Print job here...

    Debug.Print "starting to print..."

    Do

        dwWait = MsgWaitForMultipleObjectsEx(1, gDoneEvent, INFINITE, QS_ALLINPUT, &H2)

        DoEvents

    Loop Until dwWait = WAIT_OBJECT_0

    CloseHandle gDoneEvent

    DeleteTimerQueueTimer hTimerQueue, hTimer, 0

    

End Sub
 

Public Sub ExitTimerQ()

    SetEvent gDoneEvent

End Sub
 

Public Sub TimerRoutine(ByVal lpParam As Long, ByVal TimerOrWaitFired As Long)
 

    Debug.Print "TimeOut Hit test"

    ExitTimerQ  '// This needs to stay

End Sub

Open in new window

0
 

Author Comment

by:tinopener
Comment Utility
Thanks, yes, that works fine.

I've hit a new snag though. The current app is a DLL and I can't create a form in it, which is catching the event. These are options that spring to mind:

-emulate the form. Would threading do this... but does VB6 support threads?
-If threading not an option, I have the equivalent C++ library and could do it in C++.

Basically I just need a way to replicate how the form catches events.
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
Comment Utility
You don't need a form, you can use events in a class module just like a form.
' You add the same lines to the class just like the form
Dim Withevents MyPrinter As PrinterClass

0
 

Author Comment

by:tinopener
Comment Utility
Ah, ok. That's the one. Thanks for your help!!
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

This is an explanation of a simple data model to help parse a JSON feed
Since upgrading to Office 2013 or higher installing the Smart Indenter addin will fail. This article will explain how to install it so it will work regardless of the Office version installed.
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…
Viewers will learn how to properly install Eclipse with the necessary JDK, and will take a look at an introductory Java program. Download Eclipse installation zip file: Extract files from zip file: Download and install JDK 8: Open Eclipse and …

771 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

15 Experts available now in Live!

Get 1:1 Help Now