windows-nt/Source/XPSP1/NT/multimedia/media/winmm/time.c

1428 lines
42 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/******************************************************************************
Copyright (c) 1985-1999 Microsoft Corporation
Title: TIME.C : WINMM TIMER API
Version: 1.00
History:
21 Feb 1992 - Robin Speed (RobinSp) converted to Windows NT
*****************************************************************************/
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include "winmmi.h"
#define _INC_ALL_WOWSTUFF
#include "mmwow32.h"
/****************************************************************************
Structure shared between timer APIs and timer thread
****************************************************************************/
#define TDD_MINRESOLUTION 55 // in milliseconds
UINT TDD_MAXRESOLUTION; // Should be 2 ... But ...
#define TDD_MAXPERIOD 1000000 // 1000 seconds
#define TDD_MINPERIOD TDD_MAXRESOLUTION // Some apps assume this.
#define TIMER_STACK_SIZE 300
HANDLE hTimerThread; // we need this to be global
#define ROUND_MIN_TIME_TO_MS(x) (((x) + 9900) / 10000) // Special sloppy round
DWORD MinimumTime; // Kernel's version of the max res in 100ns units
typedef volatile struct {
UINT Delay; // App requested delay (ms)
UINT Resolution; // App requested resolution (ms)
LPTIMECALLBACK Callback; // Whom to call when timer fires
DWORD_PTR User; // Data to pass back when timer fires
UINT Id; // Id allocated (bottom 4 bits = slot
// id.
UINT Flags; // App's option flags
HANDLE TimerHandle; // Handle given to APP
DWORD ThreadId; // Id of requestor thread (WOW cleanup)
LARGE_INTEGER FireTime; // Time it should fire
BOOL IsWOW; // For WOW events
} TIMER_EVENT;
//
// Data integrity
//
// Held while handling resolution. ResolutionCritSec should always be
// held when using TimerData.PeriodSlots, TimerData.CurrentPeriod and
// TimerData.CurrentActualPeriod.
CRITICAL_SECTION ResolutionCritSec;
// This critical section should be held when using Events,
// TimerData.TimerNotCallingCallbackEvent, TimerData.CallbackTimerID
// and TimerData.EventCount. The critical section should also be held
// while creating the timer thread. This ensures that only one timer
// thread is created. This critical section should not be acquired if
// a thread already owns the ResolutionCritSec. A deadlock will occur
// if this critical section is acquired after the ResolutionCritSec
// is acquired.
CRITICAL_SECTION TimerThreadCritSec;
DWORD TimerThreadId;
//
// Data used to communicate with timer thread and within timer thread
//
struct {
//
// Thread control (timerThread)
//
HANDLE Event1; // Synch event - schedules thread
BOOL Started; // So WOW Cleanup doesn't deadlock
UINT CallbackTimerID; // The ID of the timer which is currently calling its' callback function.
// This value is only valid if TimerCallingCallback is TRUE.
BOOL TimerCallingCallback; // TRUE if a timer is calling its' callback function on the timer thread.
// Otherwise FALSE.
HANDLE TimerNotCallingCallbackEvent; // This event is set if no timer is calling its' callback function on
// the timer thread. Otherwise it is not set.
//
// timeGetTime stuff
//
BOOL UseTickCount;
LARGE_INTEGER InitialInterruptTick;
DWORD StartTick;
DWORD MinResolution;
//
// Internal to thread
//
UINT CurrentPeriod; // Current min res in ms
DWORD CurrentActualPeriod;
// What the kernel gave us in ms
// units
DWORD ThreadToKill; // For WOW cleanup
WORD EventCount; // For returning (fairly) unique handles
// Make this WORD for WOW compatiblity
WORD PeriodSlots[TDD_MINRESOLUTION];
// Count of what periods are set
} TimerData;
#define MAX_TIMER_EVENTS 16
TIMER_EVENT Events[MAX_TIMER_EVENTS];
/****************************************************************************
Internal functions
****************************************************************************/
BOOL TimeInitThread(void);
void TimerCompletion(UINT TimerId);
BOOL timeSetTimerEvent(TIMER_EVENT *pEvent);
DWORD timeThread(LPVOID lpParameter);
LRESULT timeThreadSetEvent(TIMER_EVENT *pEvent);
void InitializeWaitEventArrays
(
UINT* pcObjects,
HANDLE aTimers[MAX_TIMER_EVENTS + 1],
UINT aEventIndexToTimerIDTable[MAX_TIMER_EVENTS+1]
);
/*
** Read the interrupt time from the kernel
*/
static LONGLONG __inline ReadInterruptTick(VOID) {
LARGE_INTEGER InterruptTime;
// Copy the interrupt time, verifying that the 64 bit quantity (copied
// in two 32 bit operations) remains valid.
// This may mean we need to iterate around the loop.
do {
InterruptTime.HighPart = USER_SHARED_DATA->InterruptTime.High1Time;
InterruptTime.LowPart = USER_SHARED_DATA->InterruptTime.LowPart;
} while (InterruptTime.HighPart != USER_SHARED_DATA->InterruptTime.High2Time);
return InterruptTime.QuadPart;
}
/*
** Calibrate our timer
*/
VOID CalibrateTimer(VOID)
{
//
// Find out the current time(s)
//
UINT n = 100;
// We calibrate the timer by making sure that the tick count and
// interrupt tick count are in step with each other. Just in case
// the hardware goes funny we put a limit on the number of times we
// execute the loop.
while (n) {
DWORD EndTick;
--n;
TimerData.StartTick = GetCurrentTime();
TimerData.InitialInterruptTick.QuadPart = ReadInterruptTick();
EndTick = GetCurrentTime();
if (EndTick == TimerData.StartTick) {
dprintf2(("Timer calibrated, looped %d times", 100-n));
break;
}
}
}
// Calling this effectively leaks WINMM and makes sure we never
// go through the DLL exit routine
// This is used so we don't deadlock with shutting down our global threads
BOOL LoadWINMM()
{
TCHAR sz[1000];
BOOL bOK = 0 != GetModuleFileName(ghInst, sz, sizeof(sz) / sizeof(sz[0]));
if (bOK) {
HINSTANCE hInst = LoadLibrary(sz);
if (hInst != NULL) {
// ASSERT(hInst == ghInst);
} else {
bOK = FALSE;
}
}
return bOK;
}
/****************************************************************************
@doc INTERNAL
@api BOOL | TimeInit | This function initialises the timer services.
@rdesc The return value is TRUE if the services are initialised, FALSE
if an error occurs.
@comm it is not a FATAL error if a timer driver is not installed, this
routine will allways return TRUE
****************************************************************************/
BOOL NEAR PASCAL TimeInit(void)
{
//
// Find out the maximum timer resolution we can support
//
{
DWORD MaximumTime;
DWORD CurrentTime;
TimerData.MinResolution = TDD_MINRESOLUTION;
if (!NT_SUCCESS(NtQueryTimerResolution(
&MaximumTime,
&MinimumTime,
&CurrentTime))) {
TDD_MAXRESOLUTION = 10; // was 16 for NT 3.1, 10 for NT 3.5
dprintf2(("Kernel timer : using default maximum resolution"));
} else {
dprintf2((" MaximumTime = %d", MaximumTime));
dprintf2((" CurrentTime = %d", CurrentTime));
if ((MaximumTime + 9999) / 10000 < TDD_MINRESOLUTION) {
TimerData.MinResolution = (MaximumTime + 9999) / 10000;
}
//
// On the x86 it's just over 1ms minimum to we allow a little
// leeway
//
TDD_MAXRESOLUTION = max(1, ROUND_MIN_TIME_TO_MS(MinimumTime));
}
}
//
// Compute the relationship between our timer and the performance
// counter
//
CalibrateTimer();
//
// Start out slowly !
//
TimerData.CurrentPeriod = TimerData.MinResolution;
TimerData.CurrentActualPeriod = TimerData.CurrentPeriod;
return TRUE;
}
/****************************************************************************
@doc INTERNAL
@api BOOL | TimeInitThread | This function initialises the timer thread.
@rdesc The return value is TRUE if the services are initialised, FALSE
if an error occurs.
@comm it is not a FATAL error if a timer driver is not installed, this
routine will allways return TRUE
****************************************************************************/
BOOL TimeInitThread(void)
{
// Make sure winmm never gets unloaded
if (!LoadWINMM()) {
return FALSE;
}
//
// Set up events and create our thread
//
if (!NT_SUCCESS(NtCreateEvent(&TimerData.Event1,
EVENT_ALL_ACCESS,
NULL,
SynchronizationEvent,
FALSE))) { // Not signalled
return FALSE;
}
// Create an unnamed signaled manual reset event.
TimerData.TimerNotCallingCallbackEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
// CreateEvent() returns NULL if an error occurs.
if (!TimerData.TimerNotCallingCallbackEvent) {
NtClose(TimerData.Event1);
TimerData.Event1 = NULL;
return FALSE;
}
//
// The thread will start up and wait on Event1 (alertably)
//
hTimerThread = CreateThread(NULL,
TIMER_STACK_SIZE,
timeThread,
NULL,
THREAD_SET_INFORMATION,
&TimerThreadId);
if (!hTimerThread) {
CloseHandle(TimerData.TimerNotCallingCallbackEvent);
NtClose(TimerData.Event1);
TimerData.TimerNotCallingCallbackEvent = NULL;
TimerData.Event1 = NULL;
return FALSE;
}
SetThreadPriority(hTimerThread, THREAD_PRIORITY_TIME_CRITICAL);
return TRUE;
}
/****************************************************************************
@doc EXTERNAL
@api MMRESULT | timeGetSystemTime | This function retrieves the system time
in milliseconds. The system time is the time elapsed since
Windows was started.
@parm LPMMTIME | lpTime | Specifies a far pointer to an <t MMTIME> data
structure.
@parm UINT | wSize | Specifies the size of the <t MMTIME> structure.
@rdesc Returns zero.
The system time is returned in the <e MMTIME.ms> field of the <t MMTIME>
structure.
@comm The time is always returned in milliseconds.
@xref timeGetTime
****************************************************************************/
MMRESULT APIENTRY timeGetSystemTime(LPMMTIME lpTime, UINT wSize)
{
//
// !!!WARNING DS is not setup right!!! see above
//
if (wSize < sizeof(MMTIME))
return TIMERR_STRUCT;
if (!ValidateWritePointer(lpTime,wSize)) {
return TIMERR_STRUCT;
}
lpTime->u.ms = timeGetTime();
lpTime->wType = TIME_MS;
return TIMERR_NOERROR;
}
/****************************************************************************
@doc EXTERNAL
@api UINT | timeSetEvent | This function sets up a timed callback event.
The event can be a one-time event or a periodic event. Once activated,
the event calls the specified callback function.
@parm UINT | wDelay | Specifies the event period in milliseconds.
If the delay is less than the minimum period supported by the timer,
or greater than the maximum period supported by the timer, the
function returns an error.
@parm UINT | wResolution | Specifies the accuracy of the delay in
milliseconds. The resolution of the timer event increases with
smaller <p wResolution> values. To reduce system overhead, use
the maximum <p wResolution> value appropriate for your application.
@parm LPTIMECALLBACK | lpFunction | Specifies the procedure address of
a callback function that is called once upon expiration of a one-shot
event or periodically upon expiration of periodic events.
@parm DWORD | dwUser | Contains user-supplied callback data.
@parm UINT | wFlags | Specifies the type of timer event, using one of
the following flags:
@flag TIME_ONESHOT | Event occurs once, after <p wPeriod> milliseconds.
@flag TIME_PERIODIC | Event occurs every <p wPeriod> milliseconds.
@rdesc Returns an ID code that identifies the timer event. Returns
NULL if the timer event was not created. The ID code is also passed to
the callback function.
@comm Using this function to generate a high-frequency periodic-delay
event (with a period less than 10 milliseconds) can consume a
significant portion of the system CPU bandwidth. Any call to
<f timeSetEvent> for a periodic-delay timer
must be paired with a call to <f timeKillEvent>.
The callback function must reside in a DLL. You don't have to use
<f MakeProcInstance> to get a procedure-instance address for the callback
function.
@cb void CALLBACK | TimeFunc | <f TimeFunc> is a placeholder for the
application-supplied function name. The actual name must be exported by
including it in the EXPORTS statement of the module-definition file for
the DLL.
@parm UINT | wID | The ID of the timer event. This is the ID returned
by <f timeSetEvent>.
@parm UINT | wMsg | Not used.
@parm DWORD | dwUser | User instance data supplied to the <p dwUser>
parameter of <f timeSetEvent>.
@parm DWORD | dw1 | Not used.
@parm DWORD | dw2 | Not used.
@comm Because the callback is accessed at interrupt time, it must
reside in a DLL, and its code segment must be specified as FIXED
in the module-definition file for the DLL. Any data that the
callback accesses must be in a FIXED data segment as well.
The callback may not make any system calls except for <f PostMessage>,
<f timeGetSystemTime>, <f timeGetTime>, <f timeSetEvent>,
<f timeKillEvent>, <f midiOutShortMsg>,
<f midiOutLongMsg>, and <f OutputDebugStr>.
@xref timeKillEvent timeBeginPeriod timeEndPeriod
****************************************************************************/
UINT APIENTRY timeSetEvent(UINT wDelay, UINT wResolution,
LPTIMECALLBACK lpFunction, DWORD_PTR dwUser, UINT wFlags)
{
// verify the input flags
// first remove the callback type, then check that only
// time_periodic or time_oneshot are specified
if (wFlags & ~(TIME_CALLBACK_TYPEMASK | TIME_ONESHOT | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS)) {
return(0);
}
return timeSetEventInternal(wDelay, wResolution, lpFunction,
dwUser, wFlags, FALSE);
}
UINT timeSetEventInternal(UINT wDelay, UINT wResolution,
LPTIMECALLBACK lpFunction, DWORD_PTR dwUser, UINT wFlags, BOOL IsWOW)
{
UINT TimerId; // Our return value
TIMER_EVENT Event; // Event data for thread
// V_TCALLBACK(lpFunction, MMSYSERR_INVALPARAM);
//
// First check our parameters
//
if (wDelay > TDD_MAXPERIOD || wDelay < TDD_MINPERIOD) {
return 0;
}
//
// if resolution is 0 set default resolution, otherwise
// make sure the resolution is in range
//
if (wResolution > TimerData.MinResolution) {
wResolution = TimerData.MinResolution;
} else {
if (wResolution < TDD_MAXRESOLUTION) {
wResolution = TDD_MAXRESOLUTION;
}
}
if (wResolution > wDelay) {
wResolution = TimerData.MinResolution;
}
//
// Remember time if it's periodic so we get accurate long term
// timing. Otherwise we'll just use the delay.
//
if ((wFlags & TIME_PERIODIC) || IsWOW) {
Event.FireTime.QuadPart = ReadInterruptTick();
}
Event.Delay = wDelay;
Event.Resolution = wResolution;
Event.Callback = lpFunction;
Event.User = dwUser;
Event.Flags = wFlags;
Event.ThreadId = GetCurrentThreadId(); // For WOW cleanup
Event.IsWOW = IsWOW;
//
// Now set up the period to be used
//
if (timeBeginPeriod(wResolution) == MMSYSERR_NOERROR) {
EnterCriticalSection(&TimerThreadCritSec);
if (NULL == TimerData.Event1)
{
if (!TimeInitThread())
{
LeaveCriticalSection(&TimerThreadCritSec);
return(0);
}
}
TimerId = (UINT)timeThreadSetEvent(&Event);
LeaveCriticalSection(&TimerThreadCritSec);
//
// If we didn't get a good id give up
//
if (TimerId == 0) {
timeEndPeriod(wResolution);
}
} else {
TimerId = 0;
}
return TimerId;
}
/****************************************************************************
@doc EXTERNAL
@api MMRESULT | timeGetDevCaps | This function queries the timer device to
determine its capabilities.
@parm LPTIMECAPS | lpTimeCaps | Specifies a far pointer to a
<t TIMECAPS> structure. This structure is filled with information
about the capabilities of the timer device.
@parm UINT | wSize | Specifies the size of the <t TIMECAPS> structure.
@rdesc Returns zero if successful. Returns TIMERR_NOCANDO if it fails
to return the timer device capabilities.
****************************************************************************/
MMRESULT APIENTRY timeGetDevCaps(LPTIMECAPS lpTimeCaps, UINT wSize)
{
if (wSize < sizeof(TIMECAPS)) {
return TIMERR_NOCANDO;
}
if (!ValidateWritePointer(lpTimeCaps, wSize)) {
return TIMERR_NOCANDO;
}
lpTimeCaps->wPeriodMin = TDD_MINPERIOD;
lpTimeCaps->wPeriodMax = TDD_MAXPERIOD;
return MMSYSERR_NOERROR;
}
/****************************************************************************
@doc EXTERNAL
@api MMRESULT | timeBeginPeriod | This function sets the minimum (lowest
number of milliseconds) timer resolution that an application or
driver is going to use. Call this function immediately before starting
to use timer-event services, and call <f timeEndPeriod> immediately
after finishing with the timer-event services.
@parm UINT | wPeriod | Specifies the minimum timer-event resolution
that the application or driver will use.
@rdesc Returns zero if successful. Returns TIMERR_NOCANDO if the specified
<p wPeriod> resolution value is out of range.
@xref timeEndPeriod timeSetEvent
@comm For each call to <f timeBeginPeriod>, you must call
<f timeEndPeriod> with a matching <p wPeriod> value.
An application or driver can make multiple calls to <f timeBeginPeriod>,
as long as each <f timeBeginPeriod> call is matched with a
<f timeEndPeriod> call.
****************************************************************************/
MMRESULT APIENTRY timeBeginPeriod(UINT uPeriod)
{
dprintf3(("timeBeginPeriod %d", uPeriod));
dprintf4((" CurrentPeriod = %d, CurrentActualPeriod = %d",
TimerData.CurrentPeriod, TimerData.CurrentActualPeriod));
//
// See if period is in our range
//
if (uPeriod < TDD_MAXRESOLUTION) {
return TIMERR_NOCANDO;
}
if (uPeriod >= TimerData.MinResolution) {
return MMSYSERR_NOERROR;
}
EnterCriticalSection(&ResolutionCritSec);
//
// See what's happening in our slot
//
if (TimerData.PeriodSlots[uPeriod - TDD_MAXRESOLUTION] ==
0xFFFF) {
//
// Overflowed
//
LeaveCriticalSection(&ResolutionCritSec);
return TIMERR_NOCANDO;
}
TimerData.PeriodSlots[uPeriod - TDD_MAXRESOLUTION]++;
if (TimerData.PeriodSlots[uPeriod - TDD_MAXRESOLUTION] == 1 &&
uPeriod < TimerData.CurrentActualPeriod) {
DWORD NewPeriod100ns;
//
// Set the new period in our kernel driver handle
// If it's just out then use the actual minimum
//
dprintf4(("timeBeginPeriod: setting resolution %d", uPeriod));
NewPeriod100ns = uPeriod * 10000;
if (NewPeriod100ns < MinimumTime) {
NewPeriod100ns = MinimumTime;
}
if (!NT_SUCCESS(NtSetTimerResolution(
NewPeriod100ns,
TRUE,
&NewPeriod100ns))) {
dprintf1(("timeBeginPeriod: Failed to set period %d", uPeriod));
TimerData.PeriodSlots[uPeriod - TDD_MAXRESOLUTION]--;
LeaveCriticalSection(&ResolutionCritSec);
return TIMERR_NOCANDO;
} else {
//
// This slot is just started to be used and is higher
// resolution that currently set
//
TimerData.CurrentPeriod = uPeriod;
TimerData.CurrentActualPeriod =
ROUND_MIN_TIME_TO_MS(NewPeriod100ns);
LeaveCriticalSection(&ResolutionCritSec);
return MMSYSERR_NOERROR;
}
} else {
//
// No need to set period as it's already set
//
LeaveCriticalSection(&ResolutionCritSec);
return MMSYSERR_NOERROR;
}
}
/****************************************************************************
@doc EXTERNAL
@api MMRESULT | timeEndPeriod | This function clears a previously set
minimum (lowest number of milliseconds) timer resolution that an
application or driver is going to use. Call this function
immediately after using timer event services.
@parm UINT | wPeriod | Specifies the minimum timer-event resolution
value specified in the previous call to <f timeBeginPeriod>.
@rdesc Returns zero if successful. Returns TIMERR_NOCANDO if the specified
<p wPeriod> resolution value is out of range.
@xref timeBeginPeriod timeSetEvent
@comm For each call to <f timeBeginPeriod>, you must call
<f timeEndPeriod> with a matching <p wPeriod> value.
An application or driver can make multiple calls to <f timeBeginPeriod>,
as long as each <f timeBeginPeriod> call is matched with a
<f timeEndPeriod> call.
****************************************************************************/
MMRESULT APIENTRY timeEndPeriod(UINT uPeriod)
{
dprintf3(("timeEndPeriod %d", uPeriod));
dprintf4((" CurrentPeriod = %d, CurrentActualPeriod = %d",
TimerData.CurrentPeriod, TimerData.CurrentActualPeriod));
//
// Round the period to our range
//
if (uPeriod < TDD_MAXRESOLUTION) {
return TIMERR_NOCANDO;
}
if (uPeriod >= TimerData.MinResolution) {
return MMSYSERR_NOERROR;
}
EnterCriticalSection(&ResolutionCritSec);
//
// See what's happening in our slot
//
if (TimerData.PeriodSlots[uPeriod - TDD_MAXRESOLUTION] == 0) {
//
// Oops ! Overflowed
//
LeaveCriticalSection(&ResolutionCritSec);
return TIMERR_NOCANDO;
}
TimerData.PeriodSlots[uPeriod - TDD_MAXRESOLUTION]--;
if (TimerData.PeriodSlots[uPeriod - TDD_MAXRESOLUTION] == 0 &&
uPeriod == TimerData.CurrentPeriod) {
DWORD CurrentTime;
//
// This slot is just finished and was the fastest
// so find the next fastest
//
for (;uPeriod < TimerData.MinResolution; uPeriod++) {
if (TimerData.PeriodSlots[uPeriod - TDD_MAXRESOLUTION] != 0) {
break;
}
}
//
// Reset the current setting
//
NtSetTimerResolution(TimerData.CurrentActualPeriod * 10000,
FALSE,
&CurrentTime);
TimerData.CurrentActualPeriod = TimerData.MinResolution;
TimerData.CurrentPeriod = uPeriod;
if (uPeriod >= TimerData.MinResolution) {
//
// Nobody's interested in timing any more
//
} else {
//
// Set the new period in the kernel
//
DWORD NewPeriod100ns;
//
// Set the new period in our kernel driver handle
//
dprintf4(("timeEndPeriod: setting resolution %d", uPeriod));
if (!NT_SUCCESS(NtSetTimerResolution(
uPeriod * 10000,
TRUE,
&NewPeriod100ns))) {
//
// This guy's OK but everyone else is hosed
//
dprintf1(("timeEndPeriod: Failed to set period %d", uPeriod));
} else {
TimerData.CurrentActualPeriod = (NewPeriod100ns + 9999) / 10000;
}
}
}
LeaveCriticalSection(&ResolutionCritSec);
return MMSYSERR_NOERROR;
}
/****************************************************************************
@doc EXTERNAL
@api MMRESULT | timeKillEvent | This functions destroys a specified timer
callback event.
@parm UINT | wID | Identifies the event to be destroyed.
@rdesc Returns zero if successful. Returns TIMERR_NOCANDO if the
specified timer event does not exist.
@comm The timer event ID specified by <p wID> must be an ID
returned by <f timeSetEvent>.
@xref timeSetEvent
****************************************************************************/
MMRESULT APIENTRY timeKillEvent(UINT uId)
{
MMRESULT mmr;
TIMER_EVENT *pEvent;
BOOL fWaitForCallbackToEnd;
EnterCriticalSection(&TimerThreadCritSec);
// This event will be initialized if timeSetEvent() was successfully called.
if (NULL == TimerData.TimerNotCallingCallbackEvent) {
LeaveCriticalSection(&TimerThreadCritSec);
return TIMERR_NOCANDO;
}
pEvent = &Events[uId % MAX_TIMER_EVENTS];
//
// Find our event in the table and check it's there
// This also catches already completed events
//
if (pEvent->Id != uId) {
LeaveCriticalSection(&TimerThreadCritSec);
return TIMERR_NOCANDO;
}
//
// Release our event
//
timeEndPeriod(pEvent->Resolution);
pEvent->Id = 0;
if (!NT_SUCCESS(NtCancelTimer(pEvent->TimerHandle, NULL))) {
mmr = TIMERR_NOCANDO;
} else {
mmr = MMSYSERR_NOERROR;
}
NtSetEvent(TimerData.Event1, NULL);
fWaitForCallbackToEnd = ( TimerData.TimerCallingCallback &&
(uId == TimerData.CallbackTimerID) &&
(TimerThreadId != GetCurrentThreadId()) &&
(pEvent->Flags & TIME_KILL_SYNCHRONOUS) );
LeaveCriticalSection(&TimerThreadCritSec);
if ((MMSYSERR_NOERROR == mmr) && fWaitForCallbackToEnd) {
WaitForSingleObject(TimerData.TimerNotCallingCallbackEvent, INFINITE);
}
return mmr;
}
/****************************************************************************
@doc EXTERNAL
@api DWORD | timeGetTime | This function retrieves the system time
in milliseconds. The system time is the time elapsed since
Windows was started.
@rdesc The return value is the system time in milliseconds.
@comm The only difference between this function and
the <f timeGetSystemTime> function is <f timeGetSystemTime>
uses the standard multimedia time structure <t MMTIME> to return
the system time. The <f timeGetTime> function has less overhead than
<f timeGetSystemTime>.
@xref timeGetSystemTime
****************************************************************************/
DWORD APIENTRY timeGetTime(VOID)
{
if (TimerData.UseTickCount) {
//
// Use the system service
//
return GetCurrentTime();
} else {
LARGE_INTEGER Difference;
Difference.QuadPart = ReadInterruptTick() - TimerData.InitialInterruptTick.QuadPart;
return (DWORD)(Difference.QuadPart / 10000) + TimerData.StartTick;
}
}
/****************************************************************************
@doc INTERNAL
@api LRESULT | timeThread | The timer thread
@parm LPVOID | lpParameter | the thread parameter (NULL here)
@rdesc Never returns
@comm Note that this thread serializes access to the events list
****************************************************************************/
#if _MSC_FULL_VER >= 13008827
#pragma warning(push)
#pragma warning(disable:4715) // Not all control paths return (due to infinite loop)
#endif
DWORD timeThread(LPVOID lpParameter)
{
NTSTATUS nts;
UINT cObjects;
UINT uiEventIDIndex;
HANDLE aTimers[MAX_TIMER_EVENTS + 1];
UINT aEventIndexToTimerIDTable[MAX_TIMER_EVENTS + 1];
//
// Tell people it's OK to call us from DLL init sections now
//
TimerData.Started = TRUE;
InitializeWaitEventArrays( &cObjects, aTimers, aEventIndexToTimerIDTable );
//
// Sit in a loop waiting for something to do
//
for (;;) {
nts = NtWaitForMultipleObjects(
cObjects, // Number of objects (event + timers)
aTimers, // Array of handles
WaitAny, // Wait for any to signal
TRUE, // Wait Alertably (???)
NULL); // Wait forever
if (STATUS_WAIT_0 == nts)
{
// There's been some timer change (timeSetEvent, timeKillEvent),
// rebuild the array...
InitializeWaitEventArrays( &cObjects, aTimers, aEventIndexToTimerIDTable );
}
else
{
if ((nts >= STATUS_WAIT_1) && (nts <= STATUS_WAIT_0 + MAX_TIMER_EVENTS))
{
uiEventIDIndex = nts - STATUS_WAIT_0;
TimerCompletion(aEventIndexToTimerIDTable[uiEventIDIndex]);
}
else
{
WinAssert(FALSE);
}
}
}
return 1; // CreateThread() requires all threads to return a DWORD value. The
// value this thread returns has no meaning.
}
#if _MSC_FULL_VER >= 13008827
#pragma warning(pop)
#endif
void InitializeWaitEventArrays
(
UINT* pcObjects,
HANDLE aTimers[MAX_TIMER_EVENTS + 1],
UINT aEventIndexToTimerIDTable[MAX_TIMER_EVENTS+1]
)
{
UINT cObjects;
DWORD dwEventIndex;
cObjects = 0;
aTimers[cObjects++] = TimerData.Event1;
EnterCriticalSection(&TimerThreadCritSec);
for (dwEventIndex = 0; dwEventIndex < MAX_TIMER_EVENTS; dwEventIndex++)
{
if (0 != Events[dwEventIndex].Id)
{
aTimers[cObjects] = Events[dwEventIndex].TimerHandle;
aEventIndexToTimerIDTable[cObjects] = Events[dwEventIndex].Id;
cObjects++;
}
}
*pcObjects = cObjects;
LeaveCriticalSection(&TimerThreadCritSec);
}
/****************************************************************************
@doc INTERNAL
@api LRESULT | timeThread | The timer thread
@parm PVOID | ApcContext | Our context - the wave buffer header
@parm PIO_STATUS_BLOCK | The Io status block we used
@rdesc None
****************************************************************************/
BOOL timeSetTimerEvent(TIMER_EVENT *pEvent)
{
//
// Work out time to fire (and store in case timer is periodic)
//
LONGLONG Delay;
LARGE_INTEGER lDelay;
//
// Work out time to fire (and store in case timer is periodic)
//
pEvent->FireTime.QuadPart += pEvent->Delay*10000;
if (pEvent->Flags & TIME_PERIODIC) {
//
// Note that this arithmetic must allow for the case where
// timeGetTime() wraps. We do this by computing delay as
// a signed quantity and testing the sign
//
Delay = ReadInterruptTick() - pEvent->FireTime.QuadPart;
} else {
Delay = -((LONGLONG)pEvent->Delay*10000);
}
//
// If it's already fired then make the timer fire immediately
// (or at least whichever is the latest - AD 1600 or now).
// but DON'T call the callback now as we're in the TimerThreadCritSec!
//
if (Delay > 0) {
// Delay = 0; we no longer use Delay after this point
lDelay.QuadPart = 0;
} else {
lDelay.QuadPart = Delay;
}
//
// Create a timer if we haven't got one
//
if (pEvent->TimerHandle == NULL) {
HANDLE TimerHandle;
if (!NT_SUCCESS(NtCreateTimer(
&TimerHandle,
TIMER_ALL_ACCESS,
NULL,
NotificationTimer))) {
return FALSE;
}
pEvent->TimerHandle = TimerHandle;
}
//
// Possibly valid since the timer API's are not synchronized anymore
//
// WinAssert(pEvent->Id != 0);
//
// Set up a system timer
//
return
NT_SUCCESS(
NtSetTimer(pEvent->TimerHandle,
&lDelay,
NULL,
(PVOID)(DWORD_PTR)pEvent->Id,
FALSE,
0,
NULL));
}
/****************************************************************************
@doc INTERNAL
@api LRESULT | timeThreadSetEvent | Set a new event from the timer thread
@parm TIMER_EVENT * | pEvent | Our Event
@rdesc The new event id
****************************************************************************/
LRESULT timeThreadSetEvent(TIMER_EVENT *pEvent)
{
UINT i;
LRESULT lr = 0;
EnterCriticalSection(&TimerThreadCritSec);
//
// Find a free slot and fill it
//
for (i = 0; i < MAX_TIMER_EVENTS; i++) {
//
// Is the slot free ?
//
if (Events[i].Id == 0) {
pEvent->TimerHandle = Events[i].TimerHandle;
Events[i] = *pEvent;
do {
TimerData.EventCount += MAX_TIMER_EVENTS;
} while (TimerData.EventCount == 0);
Events[i].Id = i + TimerData.EventCount;
break; // Got our event
}
}
if (i == MAX_TIMER_EVENTS) {
lr = 0;
} else {
//
// Set the new event in the driver
//
if (!timeSetTimerEvent(&Events[i])) {
Events[i].Id = 0; // Failed so free our slot
lr = 0;
} else {
lr = Events[i].Id;
}
}
LeaveCriticalSection(&TimerThreadCritSec);
// Notifying timer thread of changes..
NtSetEvent(TimerData.Event1, NULL);
return lr;
}
/****************************************************************************
@doc INTERNAL
@api void | TimerCompletion | Complete a timeout event
@parm UINT | TimerId | Our timer handle
@rdesc None
****************************************************************************/
void TimerCompletion(UINT TimerId)
{
DWORD_PTR dpUser;
TIMER_EVENT *pEvent;
LPTIMECALLBACK pCallbackFunction;
EnterCriticalSection(&TimerThreadCritSec);
//
// Find out where we are
//
pEvent = &Events[TimerId % MAX_TIMER_EVENTS];
//
// Synch up with timeKillEvent
//
if (pEvent->Id != TimerId) {
LeaveCriticalSection(&TimerThreadCritSec);
return;
}
if (pEvent->IsWOW) {
//
// Adobe Premiere has to be sure the time has reached the time
// it expected. But because the timer we use for timeGetTime is
// not the same (or at least not rounded the same) as the one used
// to set the events) this need not be the case here.
//
while(pEvent->FireTime.QuadPart - ReadInterruptTick() > 0) {
Sleep(1);
}
}
switch (pEvent->Flags & TIME_CALLBACK_TYPEMASK) {
case TIME_CALLBACK_FUNCTION:
TimerData.TimerCallingCallback = TRUE;
TimerData.CallbackTimerID = pEvent->Id;
ResetEvent(TimerData.TimerNotCallingCallbackEvent);
dpUser = pEvent->User;
pCallbackFunction = pEvent->Callback;
LeaveCriticalSection(&TimerThreadCritSec);
//
// Call the callback
//
#ifdef _WIN64
DriverCallback(
*(PDWORD_PTR)&pCallbackFunction, // Function
DCB_FUNCTION, // Type of callback
(HDRVR)(DWORD_PTR)TimerId, // Handle
0, // msg = 0
dpUser, // User data
0, // dw1 = 0
0); // dw2 = 0
#else // !WIN64
if (pEvent->IsWOW) {
WOW32DriverCallback(
*(DWORD *)&pCallbackFunction, // Function
DCB_FUNCTION, // Type of callback
LOWORD(TimerId), // Handle
0, // msg = 0
(DWORD)dpUser, // User data
0, // dw1 = 0
0); // dw2 = 0
} else {
DriverCallback(
*(PDWORD_PTR)&pCallbackFunction, // Function
DCB_FUNCTION, // Type of callback
(HDRVR)TimerId, // Handle
0, // msg = 0
dpUser, // User data
0, // dw1 = 0
0); // dw2 = 0
}
#endif // !WIN64
EnterCriticalSection(&TimerThreadCritSec);
TimerData.TimerCallingCallback = FALSE;
SetEvent(TimerData.TimerNotCallingCallbackEvent);
break;
case TIME_CALLBACK_EVENT_SET:
SetEvent((HANDLE)pEvent->Callback);
break;
case TIME_CALLBACK_EVENT_PULSE:
PulseEvent((HANDLE)pEvent->Callback);
break;
}
//
// The callback may have kill it, created new timers etc!
//
if (TimerId == pEvent->Id) {
if (!(pEvent->Flags & TIME_PERIODIC)) {
UINT uResolution;
//
// One-shot - so destroy the event
//
uResolution = pEvent->Resolution; // Before we release the slot!
pEvent->Id = 0;
timeEndPeriod(uResolution);
// Not renewing the timer should remove it from the list...
NtSetEvent(TimerData.Event1, NULL);
} else {
//
// Try repeating the event
//
if (!timeSetTimerEvent(pEvent)) {
UINT uResolution;
//
// Failed - so don't keep event hanging around
//
uResolution = pEvent->Resolution; // Before we release the slot!
pEvent->Id = 0;
timeEndPeriod(pEvent->Resolution);
}
} // Periodic processing
}
LeaveCriticalSection(&TimerThreadCritSec);
}
/****************************************************************************
@doc INTERNAL
@api void | TimerCleanup | Cleanup on thread termination or DLL unload
@parm PVOID | ThreadId | Thread to clean up (WOW) or 0 for DLL unload
@rdesc None
****************************************************************************/
void TimeCleanup(DWORD ThreadId)
{
//
// Always called from DLL init routine which is protected by process
// semaphore so TimerData.ThreadToKill needs no extra protection
// This variable is an input to the timer thread which either terminates
// all timers or just those associated with the current thread (for WOW).
//
TimerData.ThreadToKill = ThreadId;
//
// Thread id of 0 means DLL cleanup
//
if (ThreadId == 0) {
if (hTimerThread) {
#ifdef WRONG
//
// we also can not synchronize with the thread at ALL ! It may not
// have gone through DLL initialization ! This means that during
// our dll routines we can not do anything with the thread unless
// we know for a fact the status of the thread !
//
// This could be fixed by setting a flag when the timer thread
// goes through initialization (process mutex held) and testing
// that flag here - but we don't exepect people to set timer
// events and unload winmm.dll
//
if (TimerData.Started) {
//
// Kill any events (only for current thread if WOW).
//
{
int i;
for (i = 0; i < MAX_TIMER_EVENTS; i++) {
if (Events[i].Id &&
(TimerData.ThreadToKill == 0 ||
TimerData.ThreadToKill == Events[i].ThreadId)) {
timeKillEvent(Events[i].Id);
}
}
}
}
// WaitForSingleObject(hTimerThread, -1);
// We cannot wait for the thread to terminate as it will
// not go through DLL exit processing while we are doing
// our DLL exit processing
#endif
}
if (TimerData.Event1) {
NtClose(TimerData.Event1);
}
} else {
//
// Per-thread Cleanup for WOW. We don't touch anything if it
// looks like nothing has run yet (so we might be caught out
// if the thread is stopped in the middle of a timeSetEvent).
//
if (TimerData.Started) {
//
// Kill any events (only for current thread if WOW).
//
{
int i;
for (i = 0; i < MAX_TIMER_EVENTS; i++) {
if (Events[i].Id &&
(TimerData.ThreadToKill == 0 ||
TimerData.ThreadToKill == Events[i].ThreadId)) {
timeKillEvent(Events[i].Id);
}
}
}
}
}
}