Accurate Timing in Windows
A Tutorial by Matthew J. W. Ratcliff
Tue  02-14-1995  10:33:08

How do you set a timed event in Windows?  Here's a simple
example:

#define TIMER_ID 1
#define A_SECOND 1000


// Message processing loop for a dialog box

        case WM_INITDIALOG:
          if (!SetTimer( hwnd, TIMER_ID, A_SECOND, NULL ))
            {
            MessageBox(hwnd,"Can't get another timer",NULL,MB_OK);
            }
          break;

        case WM_TIMER:
          MessageBeep(0);
          break;

        case WM_DESTROY:
          KillTimer( hwnd, TIMER_ID );
          EndDialog();
          break;


The above code sample enables a 1 second (MORE OR LESS) interval
timer for that dialog box.  Once per second the dialog box
receives the WM_TIMER message and a MessageBeep(0); function is
executed.  This makes for one annoying dialog box, but it
illustrates the timing functions.

Note that TIMER_ID value is arbitrary, and up to 32 of them may
be activated in the current session of windows.  If you need
multiple timers for a single application, each timer id must be
unique, but you pick the values for them.

The big problem with this method is accuracy.  It's fine if you
want to time "seconds", but if you want resolution down near a
millisecond, forget it!  The accuracy of the WM_TIMER message
processing is equivalent to the Windows function
GetCurrentTime().  This function returns the number of
milliseconds Windows has been running.  However, it is accurate
to plus or minus 55 milliseconds (1/18hz), equivalent to the
basic system timer interrupt.

If you want more accuracy, a DLL that comes with all Windows
installations is called TOOLHELP.DLL.  You will find in your
Borland or Microsoft INCLUDE directory a file called TOOLHELP.H.
It provides lots of goodies, including the function:

BOOL    WINAPI TimerCount(TIMERINFO FAR* lpTimer);

The TIMERINFO structure is defined as follows:

typedef struct tagTIMERINFO
{
    DWORD dwSize;
    DWORD dwmsSinceStart;
    DWORD dwmsThisVM;
} TIMERINFO;

To read the equivalent of "GetCurrentTime" above, but accurate to
the nearest MILLISECOND (not plus or minus 55, but plus or minus
1), you could write a function like this:

DWORD AccurateGetCurrentTime( void )
{
TIMERINFO t;
t.dwSize = sizeof(TIMERINFO);
if (!TimerCount( &t ))
  {
  MessageBox( NULL, "Failed to get Timer Count",
              NULL, MB_OK | MB_TASKMODAL );
  // TOOLHELP function broke? Use the crummy accuracy timer
  // so we return something useful.
  t.dwmsSinceStart = GetCurrentTime();
  }

return(t.dwmsSinceStart);
}

Note that if you want accurate timing and message processing, you
need Windows NT (true Preemptive Multitasking).  In Windows, each
time you process a message, some other Windows application can
take control of the system.  And if that application does a whole
bunch of file processing or computations that take a very long
time, without calling its own message processing loop during the
time consuming task, it blows your timing right out the window.
If you want an accurate timing loop, you'll need to do something
like this:

        case WM_COMMAND:
          switch( wParam )
            {
            case WM_DELAY_5_MILLISECONDS:
              long ts;
              ts = AccurateGetCurrentTime();
              while (( AccurateGetCurrentTime() - ts ) < 5);
              break;
            ...
            }

If you wanted to do some message processing, without destroying
your timing accuracy, you could possibly use the PeekMessage()
function, looking for some specific message event that might be
sent by a Virtual Device Driver (VxD's are preemptive
multitasking, and are capable of using the PostMessage function).
Beyond that, any additional message handling will destroy the
limited additional timing accuracy the TimerCount function
provides.

Run WINTIMER.EXE from Windows.  You can select delay test times
of 100 milliseconds, 1 second, or 1 minute, using either the
WM_TIMER message method or the accurate TimerCount method.  The
delay counted by TimerCount is assued to be accurate.  The delta
between the TimerCount and that computed by GetCurrentTime
functions is computed and displayed in the list box.  Try it a
bunch of times and you will notice the delta vary from between
+56 and -56 milliseconds (GetCurrentTime is +- 55 ms accuracy,
and TimerCount is +- 1 ms accuracy, their errors being additive,
worst case delta can be +- 56 ms).

You may also click on the "Time?" button.  It gets the time your
program has been running, in milliseconds, displaying the
difference between GetCurrentTime and TimerCount.

Use the WM_TIMER method to delay when you need long delay times
(over a second) and MUST HAVE windows message processing be
carried out while the delay is being timed.  If you need high
accuracy (less than a second total delay) timing, and don't want
message processing gong on while performing your delay, use the
TimerCount method.

To use the TOOLHELP.DLL functions, you need only include
<toolhelp.h> and call the functions.  You DO NOT NEED to link
with an external TOOLHELP.LIB to resolve the external function
calls, the compiler and linker take care of it for you.

Study WINTIMER.CPP.  You will find it a tidy, self contained
dialog box application.  Take a look at the Ctl3d.... function
calls.  This is all you need to enable 3D controls in your
application.  You must have ctl3d.h and ctl3d.lib to enable
access to ctl3d.dll.  (Just about everyone has a copy of
ctl3d.dll on his system...but it's not guaranteed.  With Windows
'95, you get 3d controls automatically.)

This application uses straight Windows API function calls.  It
can just as easily be compiled and linked under Microsoft or any
other Windows capable compiler.

The files in this archive are described below:

        CTL3D.LIB       - Library that enables use of CTL3D.DLL
        CTL3D.H         - Include file that lets you use 3D controls. COOL!
        CTL3D.DLL       - The 3d Controls DLL. Delete this if you
                          already have it on your system.
        LIIDSYS.H       - My portability constants. Useful stuff.
        README.TXT      - This file.
        RESOURCE.H      - Resource constants for WINTIMER.RC.
        WINTIMER.CPP    - Source code that demonstrates hirez timers.
        WINTIMER.DEF    - Windows .DEF file for this project.
        WINTIMER.EXE    - Windows executable for this project.
        WINTIMER.ICO    - Icon for this application.
        WINTIMER.MAK    - Standard Make file (Borland C++ 3.1)
        WINTIMER.PRJ    - Standard Borland C++ 3.1 Project File
        WINTIMER.RC     - Resource file for this project.
        WNUUTIL.CPP     - Windows utility functions.
        WNUUTIL.H       - Include file for windows utility functions.

Questions and comments can be directed to:

Matthew J. W. Ratcliff
GEnie                           Mat.Rat
CompuServe                      76711,1443
Internet                        m189761@flt-tst-02.mdc.com

