Solved

events and timeouts in VB6

Posted on 2008-06-24
12
1,245 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
[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
  • 6
  • 6
12 Comments
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 21861743
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
ID: 21861793
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
ID: 21869249
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
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 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 21869453
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
ID: 21869804
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
ID: 21869888
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
 

Author Comment

by:tinopener
ID: 21870043
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
ID: 21879132
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
ID: 21879444
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
ID: 21895800
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
ID: 21896025
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
ID: 21903398
Ah, ok. That's the one. Thanks for your help!!
0

Featured Post

Transaction Monitoring Vs. Real User Monitoring

Synthetic Transaction Monitoring Vs. Real User Monitoring: When To Use Each Approach? In this article, we will discuss two major monitoring approaches: Synthetic Transaction and Real User Monitoring.

Question has a verified solution.

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

Displaying an arrayList in a listView using the default adapter is rarely the best solution. To get full control of your display data, and to be able to refresh it after editing, requires the use of a custom adapter.
Whether you've completed a degree in computer sciences or you're a self-taught programmer, writing your first lines of code in the real world is always a challenge. Here are some of the most common pitfalls for new programmers.
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…
Introduction to Processes

690 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