418 lines
12 KiB
C++
418 lines
12 KiB
C++
// ==========================================================================
|
|
// Class Implementation : COXTimer
|
|
// ==========================================================================
|
|
|
|
// Source file : OXTimer.cpp
|
|
|
|
// This software along with its related components, documentation and files ("The Libraries")
|
|
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
|
|
// governed by a software license agreement ("Agreement"). Copies of the Agreement are
|
|
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
|
|
// to obtain this file, or directly from our office. For a copy of the license governing
|
|
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
|
|
|
|
// //////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "stdafx.h"
|
|
#include "OXTimer.h"
|
|
#include <mmsystem.h>
|
|
|
|
#ifndef OX_TIMER_NO_MM_LIB
|
|
#pragma comment(lib, "winmm.lib")
|
|
#endif
|
|
|
|
#ifdef _DEBUG
|
|
#undef THIS_FILE
|
|
static char BASED_CODE THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
IMPLEMENT_DYNAMIC(COXTimer, CObject)
|
|
|
|
#define new DEBUG_NEW
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Definition of static members
|
|
// ... Initialize to unkown
|
|
LARGE_INTEGER COXTimer::m_nIntervalTimerFrequency = { 0, 0 };
|
|
CMap<COXTimer*, COXTimer*, BOOL, BOOL> COXTimer::m_allTimers;
|
|
CMap<DWORD, DWORD, COXTimer*, COXTimer*> COXTimer::m_synchronizedTimers;
|
|
|
|
// Data members -------------------------------------------------------------
|
|
// protected:
|
|
// LARGE_INTEGER m_rgStartIntervalCounter;
|
|
// --- Start time of interval timer
|
|
// The unit are COUNTS of the performance counter
|
|
|
|
// LARGE_INTEGER m_rgStopIntervalCounter;
|
|
// --- Stop time of interval timer
|
|
// The unit are COUNTS of the performance counter
|
|
|
|
// static LARGE_INTEGER m_nIntervalTimerFrequency;
|
|
// --- Number of counts per second the performance counter uses
|
|
// Accurracy is 1/m_nIntervalTimerFrequency (in seconds)
|
|
// -1 = Counter is not available
|
|
// 0 = Not yet initialzed
|
|
|
|
// DWORD m_nUserData;
|
|
// --- The user data associated with this timer object
|
|
|
|
// P_OX_TIMER_NOTIFICATION m_pfNotification;
|
|
// --- Pointer to the notification function
|
|
|
|
// UINTm_nNonSynchronizedTimerNotifierID;
|
|
// --- Timer ID used by the non synchronized timer
|
|
|
|
// UINT m_nSynchronizedTimerNotifierID;
|
|
// --- Timer ID used by the synchronized timer
|
|
|
|
// BOOL m_bPeriodic;
|
|
// --- Whether the running timer is periodic or not
|
|
|
|
// static CMap<COXTimer*, COXTimer*, BOOL, BOOL> m_allTimers;
|
|
// --- Map of all the timers (only the key is used, not the value)
|
|
|
|
// static CMap<DWORD, DWORD, COXTimer*, COXTimer*> m_synchronizedTimers;
|
|
// --- Map of all running synchronized timers
|
|
// Organizes a map between the (synchronized) timer ID and the COXTimer object
|
|
// private:
|
|
|
|
// Member functions ---------------------------------------------------------
|
|
// public:
|
|
|
|
COXTimer::COXTimer()
|
|
:
|
|
m_nUserData(0),
|
|
m_pfNotification(NULL),
|
|
m_nNonSynchronizedTimerNotifierID(0),
|
|
m_nSynchronizedTimerNotifierID(0),
|
|
m_bPeriodic(FALSE)
|
|
{
|
|
m_startIntervalCounter.QuadPart = 0;
|
|
m_stopIntervalCounter.QuadPart = 0;
|
|
|
|
// ... Register this new object in the global map
|
|
m_allTimers.SetAt(this, TRUE);
|
|
|
|
ASSERT_VALID(this);
|
|
}
|
|
|
|
BOOL COXTimer::StartInterval()
|
|
{
|
|
if (::QueryPerformanceCounter(&m_startIntervalCounter))
|
|
return TRUE;
|
|
else
|
|
{
|
|
TRACE0("COXTimer::StartInterval : Failed to use performance counter\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
void COXTimer::StopInterval()
|
|
{
|
|
if (!::QueryPerformanceCounter(&m_stopIntervalCounter))
|
|
TRACE0("COXTimer::StopInterval : Failed to use performance counter\n");
|
|
}
|
|
|
|
LONGLONG COXTimer::GetInterval() const
|
|
{
|
|
return (m_stopIntervalCounter.QuadPart - m_startIntervalCounter.QuadPart) *
|
|
GetIntervalAccuracy();
|
|
}
|
|
|
|
LONGLONG COXTimer::GetIntervalAccuracy()
|
|
{
|
|
if (m_nIntervalTimerFrequency.QuadPart == 0)
|
|
{
|
|
// Variable has not yet been initialzed, do it now
|
|
if (!::QueryPerformanceFrequency(&m_nIntervalTimerFrequency))
|
|
// ... Mark as unavailable
|
|
m_nIntervalTimerFrequency.QuadPart = -1;
|
|
}
|
|
|
|
if (m_nIntervalTimerFrequency.QuadPart < 0)
|
|
return -1;
|
|
else
|
|
// ... return result in nanoseconds (1E-9 s)
|
|
return 1000000000 / m_nIntervalTimerFrequency.QuadPart ;
|
|
}
|
|
|
|
BOOL COXTimer::StartNotifier(LONGLONG nDelay, P_OX_TIMER_NOTIFICATION pfTimerNotification,
|
|
BOOL bPeriodic /* = FALSE */, BOOL bSynchronized /* = FALSE */, LONGLONG nAccuracy /* = -1 */)
|
|
{
|
|
// Convert to milliseconds because this is the smallest resolution we can use
|
|
int nMilliDelay = NANO_TO_MILLI(nDelay);
|
|
int nMilliAccuracy = 0;
|
|
|
|
if (0 <= nAccuracy)
|
|
nMilliAccuracy = NANO_TO_MILLI(nAccuracy);
|
|
else
|
|
{
|
|
// Use 1% of the total delay time and always less than a second
|
|
nMilliAccuracy = NANO_TO_MILLI(nDelay / 100);
|
|
if (1000 < nMilliAccuracy)
|
|
nMilliAccuracy = 1000;
|
|
}
|
|
|
|
// Adjust invalid requests
|
|
if (nMilliDelay <= 0)
|
|
{
|
|
TRACE0("COXTimer::StartNotifier : Requested delay time to short, using 1 millisecond\n");
|
|
nMilliDelay = 1;
|
|
}
|
|
if (nMilliAccuracy <= 0)
|
|
{
|
|
TRACE0("COXTimer::StartNotifier : Requested accuracy to high, using 1 millisecond\n");
|
|
nMilliAccuracy = 1;
|
|
}
|
|
|
|
// Kill previous timers
|
|
StopNotifier();
|
|
|
|
m_bPeriodic = bPeriodic;
|
|
if (!bSynchronized)
|
|
return StartNonSynchronizedNotifier(nMilliDelay, pfTimerNotification, bPeriodic,
|
|
nMilliAccuracy);
|
|
else
|
|
return StartSynchronizedNotifier(nMilliDelay, pfTimerNotification, bPeriodic,
|
|
nMilliAccuracy);
|
|
}
|
|
|
|
void COXTimer::StopNotifier()
|
|
{
|
|
// Stop both nonsynchronized and synchronized timer
|
|
StopNonSynchronizedNotifier();
|
|
StopSynchronizedNotifier();
|
|
|
|
m_pfNotification = NULL;
|
|
}
|
|
|
|
void COXTimer::SetUserData(DWORD nUserData /* = 0 */)
|
|
{
|
|
m_nUserData = nUserData;
|
|
}
|
|
|
|
DWORD COXTimer::GetUserData() const
|
|
{
|
|
return m_nUserData;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
void COXTimer::AssertValid() const
|
|
{
|
|
CObject::AssertValid();
|
|
}
|
|
|
|
void COXTimer::Dump(CDumpContext& dc) const
|
|
{
|
|
CObject::Dump(dc);
|
|
}
|
|
#endif //_DEBUG
|
|
|
|
COXTimer::~COXTimer()
|
|
{
|
|
// Stop possible running timer
|
|
StopNotifier();
|
|
ASSERT(m_nNonSynchronizedTimerNotifierID == 0);
|
|
ASSERT(m_pfNotification == NULL);
|
|
|
|
// Unregister this object from the global map
|
|
m_allTimers.RemoveKey(this);
|
|
}
|
|
|
|
// protected:
|
|
BOOL COXTimer::StartNonSynchronizedNotifier(int nMilliDelay, P_OX_TIMER_NOTIFICATION pfTimerNotification,
|
|
BOOL bPeriodic, int nMilliAccuracy)
|
|
// --- In : nMilliDelay : The delay in milliseconds that must elaps before the
|
|
// notificvation function is called
|
|
// pfTimerNotification : The notification function to call
|
|
// bPeriodic : Whether the function should be called periodically (TRUE)
|
|
// or just one (FALSE)
|
|
// nMilliAccuracy : The minimum accuracy in milliseconds.
|
|
// --- Out :
|
|
// --- Returns : Whether it succeeded or not.
|
|
// --- Effect : Starts the non synchronized timer notifier
|
|
{
|
|
// Store the setting of this timer
|
|
// (Must be set before starting timer so that there is no period during which the
|
|
// data members do not have a valid value)
|
|
m_nNonSynchronizedTimerNotifierID = 0;
|
|
m_pfNotification = pfTimerNotification;
|
|
|
|
// Start a new timer
|
|
m_nNonSynchronizedTimerNotifierID = ::timeSetEvent(nMilliDelay, nMilliAccuracy,
|
|
NonSynchronizedNotificationCallback, (DWORD)this,
|
|
bPeriodic ? TIME_PERIODIC : TIME_ONESHOT);
|
|
if (m_nNonSynchronizedTimerNotifierID == 0)
|
|
{
|
|
TRACE0("COXTimer::StartNonSynchronizedNotifier : Failed to start the timer\n");
|
|
// ... Reset data memebers
|
|
m_pfNotification = NULL;
|
|
}
|
|
|
|
return (m_nNonSynchronizedTimerNotifierID != 0);
|
|
}
|
|
|
|
void CALLBACK COXTimer::NonSynchronizedNotificationCallback(UINT /* uTimerID */, UINT /* uMsg */, DWORD dwUser, DWORD /* dw1 */, DWORD /* dw2 */)
|
|
// --- In : uTimerID : The timer ID
|
|
// uMsg :
|
|
// dwUser : The user data
|
|
// dw1 :
|
|
// dw2 :
|
|
// --- Out :
|
|
// --- Returns :
|
|
// --- Effect : The non synchronized callback function of all objects
|
|
{
|
|
COXTimer* pTimer = (COXTimer*)dwUser;
|
|
|
|
BOOL bDummy;
|
|
if (!m_allTimers.Lookup(pTimer, bDummy))
|
|
{
|
|
TRACE1("COXTimer::NonSynchronizedNotificationCallback : Timer object at address 0x%X does not exist anymore, ignoring\n",
|
|
pTimer);
|
|
return;
|
|
}
|
|
|
|
// ... Do some sanity checks (timer object must still exist!)
|
|
ASSERT(pTimer != NULL);
|
|
ASSERT(AfxIsValidAddress(pTimer, sizeof(COXTimer)));
|
|
ASSERT_VALID(pTimer);
|
|
|
|
// ... Delegate to object
|
|
pTimer->OnNonSynchronizedNotification();
|
|
}
|
|
|
|
void COXTimer::OnNonSynchronizedNotification()
|
|
// --- In :
|
|
// --- Out :
|
|
// --- Returns :
|
|
// --- Effect : The non synchronized callback function of this object
|
|
{
|
|
if (m_pfNotification != NULL)
|
|
m_pfNotification(this);
|
|
}
|
|
|
|
void COXTimer::StopNonSynchronizedNotifier()
|
|
// --- In :
|
|
// --- Out :
|
|
// --- Returns :
|
|
// --- Effect : Stops the non synchronized timer notifier
|
|
{
|
|
if (m_nNonSynchronizedTimerNotifierID != 0)
|
|
{
|
|
// ... Only need to timeKillEvent if the timer was periodic
|
|
if (m_bPeriodic)
|
|
VERIFY(::timeKillEvent(m_nNonSynchronizedTimerNotifierID) == TIMERR_NOERROR);
|
|
m_nNonSynchronizedTimerNotifierID = 0;
|
|
}
|
|
}
|
|
|
|
BOOL COXTimer::StartSynchronizedNotifier(int nMilliDelay, P_OX_TIMER_NOTIFICATION pfTimerNotification,
|
|
BOOL /* bPeriodic */, int /* nMilliAccuracy */)
|
|
// --- In : nMilliDelay : The delay in milliseconds that must elaps before the
|
|
// notificvation function is called
|
|
// pfTimerNotification : The notification function to call
|
|
// bPeriodic : Whether the function should be called periodically (TRUE)
|
|
// or just one (FALSE)
|
|
// nMilliAccuracy : The minimum accuracy in milliseconds.
|
|
// --- Out :
|
|
// --- Returns : Whether it succeeded or not.
|
|
// --- Effect : Starts the synchronized timer notifier
|
|
{
|
|
// Store the setting of this timer
|
|
// (Must be set before starting timer so that there is no period during which the
|
|
// data members do not have a valid value)
|
|
m_nNonSynchronizedTimerNotifierID = 0;
|
|
m_pfNotification = pfTimerNotification;
|
|
|
|
// Start a new timer
|
|
|
|
// Appearantly we have to specify a valid window handle, even when we are using a
|
|
// callback function. If we would use a callback function with a NULL HWND,
|
|
// MFC's main message loop will not dispatch the thread message.
|
|
// We will use the main window by default
|
|
#ifdef _DEBUG
|
|
if (AfxGetMainWnd()->GetSafeHwnd() == NULL)
|
|
TRACE0("COXTimer::StartSynchronizedNotifier : Function needs the HWND of the main window, may not be NULL, failing\n");
|
|
#endif
|
|
|
|
m_nSynchronizedTimerNotifierID = ::SetTimer(AfxGetMainWnd()->GetSafeHwnd(), (DWORD)this, nMilliDelay, SynchronizedNotificationCallback);
|
|
if (m_nSynchronizedTimerNotifierID != 0)
|
|
{
|
|
// Register as synchronized timer (ID - COXTimer*)
|
|
m_synchronizedTimers.SetAt(m_nSynchronizedTimerNotifierID, this);
|
|
}
|
|
else
|
|
{
|
|
TRACE0("COXTimer::StartSynchronizedNotifier : Failed to start the timer\n");
|
|
// ... Reset data memebers
|
|
m_pfNotification = NULL;
|
|
}
|
|
|
|
return (m_nSynchronizedTimerNotifierID != 0);
|
|
}
|
|
|
|
void CALLBACK COXTimer::SynchronizedNotificationCallback(HWND /*hWnd*/,
|
|
UINT /*uMsg*/,
|
|
UINT nTimerID,
|
|
DWORD /*nTime*/)
|
|
// --- In : hWnd :
|
|
// uMsg :
|
|
// uTimerID : The timer ID
|
|
// nTime : The current time
|
|
// --- Out :
|
|
// --- Returns :
|
|
// --- Effect : The synchronized callback function of all objects
|
|
{
|
|
COXTimer* pTimer = (COXTimer*)nTimerID;
|
|
|
|
BOOL bDummy;
|
|
if (!m_allTimers.Lookup(pTimer, bDummy))
|
|
{
|
|
TRACE1("COXTimer::SynchronizedNotificationCallback : Timer object at address 0x%X does not exist anymore, ignoring\n",
|
|
pTimer);
|
|
return;
|
|
}
|
|
|
|
// ... Do some sanity checks (timer object must still exist!)
|
|
ASSERT(pTimer != NULL);
|
|
ASSERT(AfxIsValidAddress(pTimer, sizeof(COXTimer)));
|
|
ASSERT_VALID(pTimer);
|
|
|
|
// ... Delegate to object
|
|
pTimer->OnSynchronizedNotification();
|
|
}
|
|
|
|
void COXTimer::OnSynchronizedNotification()
|
|
// --- In :
|
|
// --- Out :
|
|
// --- Returns :
|
|
// --- Effect : The synchronized callback function of this object
|
|
{
|
|
if (m_pfNotification != NULL)
|
|
m_pfNotification(this);
|
|
|
|
// If this is not a periodic timer, kill the timer now
|
|
if (!m_bPeriodic)
|
|
StopNotifier();
|
|
}
|
|
|
|
void COXTimer::StopSynchronizedNotifier()
|
|
// --- In :
|
|
// --- Out :
|
|
// --- Returns :
|
|
// --- Effect : Stops the synchronized timer notifier
|
|
{
|
|
if (m_nSynchronizedTimerNotifierID != 0)
|
|
{
|
|
// ... KillTimer may return FALSE if the main window has already been destroyed
|
|
::KillTimer(AfxGetMainWnd()->GetSafeHwnd(), m_nSynchronizedTimerNotifierID);
|
|
// ... Remove from global map
|
|
VERIFY(m_synchronizedTimers.RemoveKey(m_nSynchronizedTimerNotifierID));
|
|
m_nSynchronizedTimerNotifierID = 0;
|
|
}
|
|
}
|
|
|
|
// private:
|
|
|
|
// ==========================================================================
|