Code Based Counting and Callback Timers
This example shows how to create code based timers that
are more accurate and have better resolution, about 1 millisecond, than VB's
Timer control. I have implemented these code based timers in ActiveX DLLs so
they do not need to be sited on a form. This allows you to use them in
applications that run unattended and that have no visual interfaces.
Two types of timers are illustrated. A counting timer
which returns the number of milliseconds that have elapsed since Windows was
started and a callback timer. The counting time is
useful when you need stop watch type functionality - to time an operation and
see how long it took.
The callback timer works better in an event driven application since it gives
you a way to generate a single or periodic event in you application. All timer
functions are based on the Multimedia DLL - winmm.dll.
Download Source Code
This sample consists of two apps: an ActiveX DLL which implements the timer and
a small program to call the dll. First you must determine the time resolution
range available on your PC. Resolution is the size
of the slice time can be divided into. Finding the minimum and maximum
resolution is done by a call to the timeGetDevCaps
API passing it a TIMECAPS structure. This
structure has two elements. One to hold the minimum resolution and one for the
maximum.
A call to the timeBeginPeriod API sets the
resolution to use while a call to timeEndPeriod clears
it. To start timing an operation you issue a call to timeGetTime
which returns the time in milliseconds since Windows started. Remember this
time. When your operation stops you call timeGetTime
again. The difference between the two times tells how long the operation took.
In this program, the operation timed is the Sleep API.
Sleep simply halts processing of your program for a specified duration in
milliseconds.
All calls to the timer APIs are encapsulated in the DLL. The DLL exposes
properties used to retrieve the minimum and maximum resolution as well as the
return value from the timeGetTime function.
Be aware that the value returned by timeGetTime is an unsigned number and can
hold a value of 2^32-1 milliseconds (approximately 49 days). However, VB cannot
handle unsigned integers and will only hold a number half that size. If the
computer has been running for a long time, the number may switch from a
positive to a negative number. You should handle this event.
Simply put, a callback is when Windows calls a
procedure in your application. To use callback functions with certain APIs you
pass the address of your procedure to be called back using VB's
AddressOf operator. I'm not going to get into the details of
callbacks here, you can see my Enumerate Windows sample for more information.
As the name implies the callback timer calls a procedure in your application
when it triggers. That procedure can then execute the necessary code.
The callback timer has a set of functions, timeSetEvent()
and timeKillEvent(), for creating and killing a
timer. As with the counting time, timeBeginPeriod and
timeEndPeriod set the minimum timer period.
timeSetEvent specifies the timer's operating mode and takes several
arguments, two of which are the callback procedure's address and the delay
period. When the delay period expires the timer calls the callback procedure.
Another parameter is the flags argument which tells the timer to act as a
one-shot timer or to fire repeatedly.
Much of the details of the callback timer are straightforward and are left to
the code itself. One important issue is that the callback procedure runs in a
separate thread from your application. This poses a problem. All
statements needed to raise an event in the VB class are off limits because they
are run on a different thread. To get around this, an invisible window is
created and subclassed to capture a special message indicating that the timer
has fired. The timer procedure then uses the PostMessage
API to send the appropriate message. When the subclassing procedure receives
the message, it executes on the main thread so you can use all the normal VB
statements. There's more. Using the PostMessage API within a callback procedure
is problematic. It must be placed inside a type library.
Once all that is done, the sample application is simple. The key is to declare
the timer object using the WithEvents keyword so
the timer event is available.
Since the DLLs are ActiveX components, they must be
registered on your PC. I have included .bat files which call
Regsvr32.exe (available on your VB CD) to do this. You must first
edit the .bat files and make sure the paths are correct. Then select Project |
References in the calling app and insure that the DLL is correctly referenced.
To unregister the DLLs when you are through, edit and run the uninstall batch
program. Note that the path that follows regsvr32 must be in DOS 8.3 format.
To step through the DLL code in the development environment, start the DLL
project and press Control F5 to run it. Then start the calling app in a second
instance of VB. Go to Project | References and reference the correct timer
project (StopWatch Timer or Multimedia Callback Timer). The correct reference
is the one whose path points to the Visual Basic Project. Note that stepping
through a callback procedure is sure to create GPFs.
Code based timers are still subject to the operating system's load and
preemptive multitasking nature. For best results, close all unnecessary
applications.
Register and reference the DLLs a stated above.
Run the counting timer and enter a duration. Click the Start Timer button to
time the Sleep operation. Try this with various durations. Experiment with
different resolutions to test the accuracy of the timer.
Do basically the same for the callback timer. Set the repeating and enable
options and set the interval.
|