Hart.gif (5380 bytes)Win32 System Programming

Return to Addison-Wesley Home Page  


Return to Top Page

Beep.c - Revised


/* beep.c. Periodic alarm. */
/* Usage: beep period-in-seconds */
/* The time period is in seconds and includes the time the alarm is on.
   The beeper goes off periodically until the user presses the message box button. */

/* Note that very little is achieved here as the main program cannot
   do anything until the user presses the button, causing a return from
   MessageBox. This is not very satisfactory for a console application
   without its own main message loop. As an alternative, you could
   simply have a loop such as the following:

         while (TRUE) {
                 Sleep (Period);
                 .... Do whatever is in the callback function
         }

Now, if you really want to do something else but still have the action of the callback function carried out periodically, you could create a separate process or thread containing the above loop. Threads are discussed in Chapter 10, and the Server program in Chapter 9 has a separate process to carry out periodic actions. You might want to give the thread or process a high priority (also see Chapter 10).


TimeBeep.c


This program uses a "waitable" timer, which is a named, sharable, securable object. Waitable timers were introduced in Windows NT Version 5.0 and are not available in Windows 95/98. Furthermore, library support was introduced in Visual C++ Version 5.0.

The naming is similar to Chapter 6's memory-mapped objects and Chapter 11's synchronization objects (semaphores, events, and mutexes).

First, the program calls CreateWaitableTimer to obtain a handle. The second parameter determines whether or not the timer is a "synchronization timer" or a "manual reset notification timer" (the documentation does not explain the difference, so some remarks will follow). This example uses a synchronization timer, but you can change the comment to obtain a notification timer. Notice that there is also an OpenWaitableTimer function.

Next, SetWaitableTimer specifies the initial signal time as either a positive absolute time or a negative relative time; we use a relative time. The interval between signals is also specified. FILETIMEs are used to specify these times. Finally, there is a parameter specifying the time-out function (completion routine) to be called.

Having set the timer, you can now either call SleepEx (see Chapter 13) or WaitForSingleObjectEx (also see Chapter 13). The first technique, the default in this example (the wait is commented out), blocks this thread until the timer is signaled and the timer function completes.

Here are the four combinations and the results, with some interpretation:

  1. Synchronization Timer, SleepEx. This produces the desired result. The time-out function (completion routine) is called, properly, every time period. The system enters the alertable wait state when SleepEx is called. SleepEx returns after the timer function executes (note the INFINITE time-out).
  2. Synchronization Timer, WaitForSingleObjectEx. You do not get the results you want. Assume, for the sake of argument, that the time-out period is 5 seconds. The initial printout of count occurs immediately (showing 0). The next printout occurs 5 seconds later, and count is still 0 (showing that the time-out function, Beeper, has not executed. The third printout, giving a count of 1, occurs immediately, and the fourth printout, 5 seconds later, also shows 1. This pattern continues indefinitely, showing that the timer handle is initially signaled. Furthermore, we return from the alertable wait before the time-out function executes and increments count (this would not be the case with extended I/O), and the handle state is not reset, allowing the following wait to be released immediately. It appears that the wait that occurs after the time-out routine executes, as well as the first wait call, resets the timer handle to unsignaled.
  3. Manual Reset Notification Timer, SleepEx. This also produces the desired result. The timer function is called, properly, every time period. The system enters the alertable wait state when SleepEx is called. SleepEx returns after the timer function executes (note the INFINITE timeout). Notice that we get the same results as with Case 1; it does not matter whether the timer is a notification or synchronization timer.
  4. Manual Reset Notification Timer, WaitForSingleObjectEx. The timer handle is never reset to the unsignaled state, and the Beeper function is never invoked. Consequently, count never increases and the main loop runs continuously without any delays.

Conclusion: You should use SleepEx regardless of the timer type. Notification timer behavior is counter-intuitive. I would have expected that such a timer would be required invoke the completion routine after the thread enters the alertable wait state. Conversely, I would have thought that a synchronization timer would be used with a simple wait function (no Ex suffix); this will, in fact, work correctly if you do not have a completion routine.

Use CancelWaitableTimer to cancel the last effect of a previous SetWaitableTimer, although it will not change the signaled state of the timer. Use another SetWaitableTimer call to do that.

Additional Comments: A manual reset timer remains reset until you set it again (with SetWaitableTimer). Therefore, the MR timer would appear to have no utility as a periodic timer.

Thanks to Andrew Tucker for some helpful comments on this section.


/* Chapter 8 - New Program. TimeBeep.c. Periodic alarm.
   This is an alternative implementation to the one used
   in beep.c, which used the main message loop. You may want
   to read Chapter 13 (p. 277) first as the "alertable
   wait functions" are required.
   ALSO: This program uses a console control handler to catch
   control-c signals; this subject is covered at the end of
   Chapter 8.*/

/* Usage: TimeBeep Period. */
/* This implementation uses the "waitable timers" introduced
   in NT 4.0 and supported by Visual C++ Ver 5.0. 
   The waitable timers are sharable
   named objects (their names share the same space used by
   synchronization and memory mapped objects).
   The waitable timer is a synchronization object that you
   can wait on for the timer to signal. Alternatively, you can
   have a completion routine executed in the same thread by
   entering an "alertable wait state" just as with extended,
   ("completion routine") I/O (see Chapter 13). */

#define _WIN32_WINNT 0x0400     /* Required in <winbase.h> to define
          waitable timer functions. This is not explained in the documentation.
          It indicates that you need NT 4.0. */

#include "EvryThng.h"

static BOOL WINAPI Handler (DWORD CntrlEvent);
static VOID APIENTRY Beeper (LPVOID, DWORD, DWORD);
volatile static BOOL Exit = FALSE;

HANDLE hTimer;

int _tmain (int argc, LPTSTR argv [])
{
     DWORD Count = 0, Period;
     LARGE_INTEGER DueTime;

     if (argc >= 2)
          Period = _ttoi (argv [1]) * 1000;
     else ReportError (_T ("Usage: beep period"), 1, FALSE);

     if (!SetConsoleCtrlHandler (Handler, TRUE))
          ReportError (_T ("Error setting event handler"), 2, TRUE);

     DueTime.QuadPart = -(LONGLONG)Period * 10000;
            /* Due time is negative for first time out relative to
               current time. Period is in ms (10**-3 sec) whereas
               the due time is in 100 ns (10**-7 sec) units to be
               consistent with a FILETIME. */

     hTimer = CreateWaitableTimer (NULL,   /* Security attributes */
     /*TRUE*/  FALSE, /* Not manual reset (not a "notification timer") but
                        a "synchronization timer". Documentation does not
                        explain the distinction, but see comments at top of this
                        page. */
          NULL       /* Do not name the timer - name space is shared with
                        events, mutexes, semaphores, and mem map objects */);
     
     if (hTimer == NULL) 
          ReportError (_T ("Failure creating waitable timer"), 3, TRUE);
     if (!SetWaitableTimer (hTimer, &DueTime /* relative time of first signal */,
            Period  /* Time period in ms */,
            Beeper  /* Timer function */,
            &Count  /* Parameter passed to timer function */,
            TRUE    /* Does not apply - do not use it. */))
          ReportError (_T ("Failure setting waitable timer"), 4, TRUE);

/* Enter the main loop */
     while (!Exit) {
          _tprintf (_T("Count = %d\n"), Count); 
                   /* Count is increased in the timer routine */
                   /* Enter an alertable wait state, enabling the timer routine.
                      The timer handle is a synchronization object, so you can 
                      also wait on it. */
          SleepEx (INFINITE, TRUE);
//        WaitForSingleObjectEx (hTimer, INFINITE, TRUE); 
			/* See comment at the top of this page */
     }

     _tprintf (_T("Shut down. Count = %d"), Count);
     CancelWaitableTimer (hTimer);
     CloseHandle (hTimer);
     return 0;
}   

static VOID APIENTRY Beeper (LPVOID lpCount,
          DWORD dwTimerLowValue, DWORD dwTimerHighValue)
{
     *(LPDWORD)lpCount = *(LPDWORD)lpCount + 1;

     Beep (1000 /* Frequency */, 250 /* Duration (ms) */);
     return;
}

BOOL WINAPI Handler (DWORD CntrlEvent)
{
     Exit = TRUE;
     return TRUE;
}