|
|
/* timers.c -- Multiplexed timer routines -- run several timers off one
* Windows timer. * * Copyright 1994 by Hilgraeve, Inc. -- Monroe, MI * All rights reserved * * $Revision: 6 $ * $Date: 5/29/02 2:17p $ */
#include <windows.h>
#pragma hdrstop
#include "stdtyp.h"
#include <tdll\assert.h>
#include "mc.h"
#include "session.h"
#include "timers.h"
typedef struct s_timer ST_TIMER; typedef struct s_timer_mux ST_TIMER_MUX;
struct s_timer_mux { HSESSION hSession; HWND hWnd; UINT uiID; UINT_PTR uiTimer; UINT uiLastDuration; int fInMuxProc; ST_TIMER *pstFirst; ST_TIMER *pstCurrent; };
struct s_timer { HSESSION hSession; ST_TIMER *pstNext; ST_TIMER_MUX *pstTimerMux; long lInterval; long lLastFired; long lFireTime; void *pvData; TIMERCALLBACK pfCallback; };
void TimerInsert(ST_TIMER_MUX *pstTimerMux, ST_TIMER *pstTimer); int TimerSet(ST_TIMER_MUX *pstTimerMux);
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
* FUNCTION: TimerMuxCreate * * DESCRIPTION: * Creates a Timer Multiplexer from which any number of individual timers * can be created. A Timer Multiplexer uses only one Windows Timer now * matter how many individual timers are created from it. * * ARGUMENTS: * pHTM -- A pointer to an HTIMERMUX handle. This handle must be used in * subsequent calls to CreateTimer * * RETURNS: * TIMER_OK if successful * TIMER_NOMEM if there is insufficient memory * TIMER_NOWINTIMER if there are no Windows timers available * TIMER_ERROR if invalid parameters are passed. */ int TimerMuxCreate(const HWND hWnd, const UINT uiID, HTIMERMUX * const pHTM, const HSESSION hSession) { //
// sessQueryTimerMux() locks the session's TimerMux
// critical section. Call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section. REV: 5/21/2002
//
HTIMERMUX hTM = sessQueryTimerMux(hSession);
int iReturnVal = TIMER_OK; ST_TIMER_MUX *pstTM;
if (!hWnd || !pHTM) { assert(FALSE); return TIMER_ERROR; }
if ((pstTM = malloc(sizeof(ST_TIMER_MUX))) == NULL) { iReturnVal = TIMER_NOMEM; } else { pstTM->hSession = hSession; pstTM->hWnd = hWnd; pstTM->uiID = uiID; pstTM->uiTimer = 0; pstTM->uiLastDuration = 0; pstTM->pstFirst = (ST_TIMER *)0; pstTM->pstCurrent = (ST_TIMER *)0; pstTM->fInMuxProc = FALSE;
iReturnVal = TimerSet(pstTM);
DbgOutStr("TimerMux handle %#lx created.\r\n", pstTM, 0, 0, 0, 0); }
if (iReturnVal != TIMER_OK) { (void)TimerMuxDestroy((HTIMERMUX *)&pstTM, hSession); }
*pHTM = (HTIMERMUX)pstTM;
//
// Don't forget to call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section locked in
// sessQueryTimerMux(). REV: 5/21/2002
//
sessReleaseTimerMux(hSession);
return iReturnVal; }
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
* FUNCTION: TimerMuxDestroy * * DESCRIPTION: * Destroys a timer multiplexer and any timers still active * * ARGUMENTS: * phTM -- Pointer to a timer mux handle of type HTIMERMUX created by * an earlier call to TimerMuxCreate. The value pointed to will * be set to NULL after the TimerMux is destroyed * * RETURNS: * TIMER_OK */ int TimerMuxDestroy(HTIMERMUX * const phTM, const HSESSION hSession) { //
// sessQueryTimerMux() locks the session's TimerMux
// critical section. Call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section. REV: 5/21/2002
//
HTIMERMUX hTM = sessQueryTimerMux(hSession);
ST_TIMER *pstTimer; ST_TIMER_MUX *pstTimerMux; assert(phTM);
pstTimerMux = (ST_TIMER_MUX *)*phTM;
if (pstTimerMux) { while (pstTimerMux->pstFirst) { pstTimer = pstTimerMux->pstFirst; (void)TimerDestroy((HTIMER *)&pstTimer); } if (pstTimerMux->uiTimer) { DbgOutStr("KillTimer (timers.c)\r\n",0,0,0,0,0); (void)KillTimer(pstTimerMux->hWnd, pstTimerMux->uiID); } free(pstTimerMux); pstTimerMux = NULL;
DbgOutStr("TimerMux handle 0x%lx destroyed.\r\n", pstTimerMux, 0, 0, 0, 0); }
*phTM = (HTIMERMUX)0;
//
// Don't forget to call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section locked in
// sessQueryTimerMux(). REV: 5/21/2002
//
sessReleaseTimerMux(hSession);
return TIMER_OK; }
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
* FUNCTION: TimerCreate * * DESCRIPTION: * Creates a timer that will call a registered call back function at * regular intervals specified in milliseconds. Once created, a timer * can be destroyed by calling TimerDestroy(). TimerDestroy can be called * from within the timer callback procedure. * * ARGUMENTS: * hTM -- A timer multiplexer handle returned from a call to * TimerMuxCreate * phTimer -- Pointer to a variable of type HTIMER to receive the * handle of the new timer. * lInterval -- The timer interval in milliseconds. The callback function * will be called repeatedly at roughly this interval until * the timer is destroyed. The timer will operate at a minimum * resolution depending on system capabilities. In Windows 3.x, * the minimum resolution is 55 msecs. Due to the operation * of the underlying Windows timer function, any interval may * be extended an arbitrary amount of time. * pfCallback -- Pointer to a function to be called after each interval. * This function should be of type TIMER_CALLBACK. The * value passed should be the result of calling * MakeProcInstance on the actual callback function. * * This function should accept two arguments: a void ptr * value which will contain any value supplied in the * pvData argument described below; and an unsigned long * which will be set to the actual duration of the most * recent interval in milliseconds. * pvData -- Can contain any arbitrary data. This value will be * associated with the timer created and will be passed as * an argument when the callback funtion is called. * * RETURNS: * TIMER_OK if timer could be created. * TIMER_NOMEM if there was insufficient memory * TIMER_NOWINTIMER if no Windows timers are available * TIMER_ERROR if parameters were invalid (also generates an assert) */ int TimerCreate(const HSESSION hSession, HTIMER * const phTimer, long lInterval, const TIMERCALLBACK pfCallback, void *pvData) { //
// sessQueryTimerMux() locks the session's TimerMux
// critical section. Call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section. REV: 5/21/2002
//
HTIMERMUX hTM = sessQueryTimerMux(hSession);
ST_TIMER_MUX * const pstTimerMux = (ST_TIMER_MUX *)hTM; ST_TIMER *pstTimer = (ST_TIMER *)0; int iReturnVal = TIMER_OK;
assert(pstTimerMux && pfCallback);
if (pstTimerMux) { // Guard against a zero interval
if (lInterval == 0L) { ++lInterval; }
if (!pstTimerMux || !pfCallback) { iReturnVal = TIMER_ERROR; } else if ((pstTimer = malloc(sizeof(*pstTimer))) == NULL) { iReturnVal = TIMER_NOMEM; } else { pstTimer->hSession = hSession; pstTimer->pstNext = (ST_TIMER *)0; pstTimer->pstTimerMux = pstTimerMux; pstTimer->lInterval = lInterval; pstTimer->lLastFired = (long)GetTickCount(); pstTimer->lFireTime = pstTimer->lLastFired + lInterval; pstTimer->pvData = pvData; pstTimer->pfCallback = pfCallback;
TimerInsert(pstTimerMux, pstTimer);
// Following code caused problems when called from a thread other
// than the main thread
// if ((iReturnVal = TimerSet(pstTimerMux)) != TIMER_OK)
// (void)TimerDestroy((HTIMER *)&pstTimer);
PostMessage(pstTimerMux->hWnd, WM_FAKE_TIMER, 0, 0);
DbgOutStr("Timer handle %#lx (%#lx) created.\r\n", pstTimer, pstTimerMux, 0, 0, 0); }
if (phTimer) { *phTimer = (HTIMER)pstTimer; }
}
//
// Don't forget to call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section locked in
// sessQueryTimerMux(). REV: 5/21/2002
//
sessReleaseTimerMux(hSession);
return iReturnVal; }
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
* FUNCTION: TimerDestroy * * DESCRIPTION: * Destroys a timer created with TimerCreate. This routine can be called * to destroy a timer from within its own callback functionl * * ARGUMENTS: * hTimer -- A timer handle returned from a call to TimerCreate. * * RETURNS: * TIMER_OK if the timer is found and destroyed. * TIMER_ERROR if the handle could not be found. */ int TimerDestroy(HTIMER * const phTimer) { int iReturnVal = TIMER_OK; ST_TIMER stDummy; ST_TIMER *pstTimer = (ST_TIMER *)*phTimer; ST_TIMER *pstScan; ST_TIMER *pstFound; ST_TIMER_MUX *pstTimerMux;
assert(phTimer);
if (pstTimer) { //
// sessQueryTimerMux() locks the session's TimerMux
// critical section. Call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section. REV: 5/21/2002
//
HTIMERMUX hTM = sessQueryTimerMux(pstTimer->hSession); //
// Get the session handle for call to sessReleaseTimerMux() later.
//
const HSESSION hSession = pstTimer->hSession;
// Get pointer to parent struct
pstTimerMux = pstTimer->pstTimerMux;
if (pstTimerMux) { // If a timer is being destroyed from within its own callback, it
// has already been removed from the timer chain. Setting
// pstTimerMux->pstCurrent to NULL will prevent it from being
// rescheduled.
if (pstTimer == pstTimerMux->pstCurrent) { free(pstTimer); pstTimer = NULL; DbgOutStr("Timer destroyed 0x%lx\r\n", (LONG)pstTimer, 0, 0, 0, 0); *phTimer = (HTIMER)0; pstTimerMux->pstCurrent = (ST_TIMER *)0; }
else { // Set up dummy node at head of list to avoid a bunch of
// special cases
stDummy.pstNext = pstTimerMux->pstFirst; pstScan = &stDummy;
// Scan through list for match, maintaining pointer to the
// node BEFORE
while ((pstFound = pstScan->pstNext) != (ST_TIMER *)0) { if (pstFound == pstTimer) { break; } pstScan = pstFound; }
// pstFound will be NULL if timer was not in list, otherwise
// pstFound is the node to remove and pstScan is the node
// prior to it
if (!pstFound) { iReturnVal = TIMER_ERROR; } else { pstScan->pstNext = pstFound->pstNext; DbgOutStr("Timer handle 0x%lx destroyed.\r\n", pstFound, 0, 0, 0, 0); free(pstFound); pstFound = NULL;
// If we just destroyed a timer from within its own callback,
// leave a sign so the timer proc will know
if (pstFound == pstTimerMux->pstCurrent) pstTimerMux->pstCurrent = (ST_TIMER *)0;
// Remove dummy node from start of list
pstTimerMux->pstFirst = stDummy.pstNext; *phTimer = (HTIMER)0; } } } else { iReturnVal = TIMER_ERROR; }
//
// Don't forget to call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section locked in
// sessQueryTimerMux(). REV: 5/21/2002
//
sessReleaseTimerMux(hSession); }
return iReturnVal; }
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
* FUNCTION: TimerMuxProc * * DESCRIPTION: * This function should be called by the window proc of the window whose * handle was passed to TimerMuxCreate when a WM_TIMER message is received. * It uses one Windows timer to control any number of individual multiplexed * timers. * * ARGUMENTS: * hSession -- The session to retreive the TimerMux handle from. * * RETURNS: * nothing */ void TimerMuxProc(const HSESSION hSession) { //
// sessQueryTimerMux() locks the session's TimerMux
// critical section. Call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section. REV: 5/21/2002
//
HTIMERMUX hTM = sessQueryTimerMux(hSession);
ST_TIMER *pstScan; ST_TIMER_MUX * const pstTimerMux = (ST_TIMER_MUX *)hTM; long lNow; TIMERCALLBACK *pfCallback;
// Callbacks to timer procs to the printing routines can take a
// long time because of paper-out, etc. Since the AbortProc in
// the printer routines yields via a message loop, it is possible
// (read probable) that we can recursively enter this routine.
// The fInMuxProc flag guards against such an event. - mrw
if (!pstTimerMux->fInMuxProc) { pstTimerMux->fInMuxProc = TRUE; } else { return; }
lNow = (long)GetTickCount(); DbgOutStr("%ld ", lNow, 0, 0, 0, 0);
// In the following routine, note that the node associated with the
// current call back is NOT linked into the timer chain during the
// call back. This allows TimerDestroy to be called on a timer from
// within its own call back. It is also OK to call TimerCreate from
// within call backs.
// Since timer ticks can be delayed, more than one event may have expired
pstScan = pstTimerMux->pstFirst; while (pstScan && lNow > pstScan->lFireTime) { // Keep track of which timer is being called
pstTimerMux->pstCurrent = pstScan;
// Remove current node from list (will be added back in later if
// not destroyed)
pstTimerMux->pstFirst = pstScan->pstNext;
pfCallback = &pstScan->pfCallback;
// Give up the critical section while doing the callback so
// a lengthy call back won't delay any other threads
sessReleaseTimerMux(hSession);
// Activate the call back function
(*pfCallback)(pstScan->pvData, lNow - pstScan->lLastFired);
hTM = sessQueryTimerMux(hSession); assert(pstTimerMux == (ST_TIMER_MUX *)hTM);
lNow = (long)GetTickCount(); DbgOutStr("%ld ", lNow, 0, 0, 0, 0);
// If timer was destroyed during callback, pstTimerMux->pstCurrent will have
// been sent to NULL; otherwise reschedule this timer
if ((pstScan = pstTimerMux->pstCurrent) != (ST_TIMER *)0) { DbgOutStr("Reschedule ", 0, 0, 0, 0, 0); // Reschedule timer
pstScan->lLastFired = lNow; pstScan->lFireTime = lNow + pstScan->lInterval;
// link this timer back into the list
TimerInsert(pstTimerMux, pstScan); pstTimerMux->pstCurrent = (ST_TIMER *)0; }
// First node on list is always the next one due to fire
pstScan = pstTimerMux->pstFirst; }
(void)TimerSet(pstTimerMux);
pstTimerMux->fInMuxProc = FALSE; // LeaveCriticalSection(&pstTimerMux->critsec);
//
// Don't forget to call sessReleaseTimerMux() to unlock
// the session's TimerMux critical section locked in
// sessQueryTimerMux(). REV: 5/21/2002
//
sessReleaseTimerMux(hSession);
return; }
// INTERNAL ROUTINES
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
* FUNCTION: TimerInsert * * DESCRIPTION: * Links a timer control node into the linked list of all multiplexed timers. * The list is maintained in order by when the node is due to fire. * * ARGUMENTS: * pstTimerMux -- Handle to the timer multiplexer. * pstTimer -- Pointer to a node to be inserted. * * RETURNS: * nothing */ void TimerInsert(ST_TIMER_MUX *pstTimerMux, ST_TIMER *pstTimer) { ST_TIMER *pstScan;
pstScan = pstTimerMux->pstFirst;
// If there are no other nodes in the list or if the new timer is
// scheduled before the first one in the list, link new one in first
if (!pstScan || pstTimer->lFireTime < pstScan->lFireTime) { pstTimer->pstNext = pstScan; pstTimerMux->pstFirst = pstTimer; } else { // Insert sorted by lFireTime
while (pstScan->pstNext && pstScan->pstNext->lFireTime < pstTimer->lFireTime) { pstScan = pstScan->pstNext; }
// Link into chain
pstTimer->pstNext = pstScan->pstNext; pstScan->pstNext = pstTimer; }
return; }
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
* FUNCTION: TimerSet * * DESCRIPTION: * Sets up a Windows timer using SetTimer to fire when the next multiplexed * timer needs attention. Since the Window timer operates with an interval * specified in a USHORT, there are times when the timer may have to be * set to go off before the next required interval. If there are no * multiplexed timers to be serviced, the timer is set to its maximum time * anyway. By keeping one timer whether we need it or not, we guarantee * that one will be available when we DO need it. * * ARGUMENTS: * none * * RETURNS: * TIMER_OK if successful. * TIMER_NOWINTIMER if no Windows timers were available */ int TimerSet(ST_TIMER_MUX *pstTimerMux) { UINT uiDuration = 100000; int iReturnVal = TIMER_OK; long lTickCount;
if (pstTimerMux->pstFirst) { lTickCount = (long)GetTickCount();
if (pstTimerMux->pstFirst->lFireTime <= lTickCount) uiDuration = 1; // Timer has already expired
else uiDuration = (UINT)(pstTimerMux->pstFirst->lFireTime - lTickCount); }
if (pstTimerMux->uiTimer == 0 || uiDuration != pstTimerMux->uiLastDuration) { // if (pstTimerMux->uiTimer != 0)
// {
// DbgOutStr("KillTimer (timers.c)\r\n",0,0,0,0,0);
// fResult = KillTimer(pstTimerMux->hWnd, pstTimerMux->uiID);
// assert(fResult);
// }
pstTimerMux->uiTimer = SetTimer(pstTimerMux->hWnd, pstTimerMux->uiID, uiDuration, NULL);
DbgOutStr("SetTimer (timers.c)\r\n", 0,0,0,0,0);
if (pstTimerMux->uiTimer == 0) { iReturnVal = TIMER_NOWINTIMER; }
pstTimerMux->uiLastDuration = uiDuration; }
return (iReturnVal); }
// End of timers.c
|