Last active
September 1, 2022 07:14
-
-
Save mjs3339/b98bbf4075be0176ac521c9875652dfe to your computer and use it in GitHub Desktop.
C# Multimedia Timer TimeSetEvent TimeKillEvent
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MMTimer : IDisposable | |
{ | |
private const int EventTypePeriodic = 1; | |
private const uint STATUS_SUCCESS = 0; | |
private const uint STATUS_TIMER_RESOLUTION_NOT_SET = 0xC0000245; | |
private readonly MultimediaTimerCallback Callback; | |
private bool disposed; | |
private bool enabled; | |
private bool highestPossibleResolution; | |
private int interval, resolution; | |
private volatile uint timerId; | |
public MMTimer() | |
{ | |
Callback = TimerCallbackMethod; | |
Interval = 10; | |
} | |
[Description("Enables or Disables this timer event.")] | |
public bool Enabled | |
{ | |
get => enabled; | |
set | |
{ | |
if (value == enabled) | |
return; | |
enabled = value; | |
if (enabled) | |
Start(); | |
else | |
Stop(); | |
} | |
} | |
[Description("Data associated with this object.")] | |
public object Tag { get; set; } | |
public int Interval | |
{ | |
get => interval; | |
set | |
{ | |
if (disposed) | |
throw new ObjectDisposedException("MMTimer"); | |
if (value < 0) | |
throw new ArgumentOutOfRangeException("value"); | |
interval = value; | |
var tc = GetTimerCaps(); | |
if (value < tc.periodMin || value > tc.periodMax) | |
throw new Exception($"Value{value} is out of range.\nMust be greater than or equal to{tc.periodMin} or \n" + | |
$"Less than or equal to{tc.periodMax} (Milliseconds)."); | |
} | |
} | |
[Description("If set to true the effect is system wide.")] | |
public bool HighestPossibleResolution | |
{ | |
get => highestPossibleResolution; | |
set | |
{ | |
if (IsRunning) | |
Stop(); | |
highestPossibleResolution = value; | |
var DesiredResolutionIn100nsUnits = 5000u; | |
var CurrentResolution = 0u; | |
lock (this) | |
{ | |
switch (NtSetTimerResolution(DesiredResolutionIn100nsUnits, highestPossibleResolution, ref CurrentResolution)) | |
{ | |
case STATUS_SUCCESS: | |
Tag = $"The current resolution is {CurrentResolution / 10000D} Ms."; | |
break; | |
case STATUS_TIMER_RESOLUTION_NOT_SET: | |
Tag = "Error setting resolution."; | |
break; | |
default: | |
throw new Exception("NtSetTimerResolution call failed."); | |
} | |
} | |
} | |
} | |
public bool IsRunning => timerId != 0; | |
public void Dispose() | |
{ | |
Dispose(true); | |
} | |
private void Dispose(bool disposing) | |
{ | |
if (disposed) | |
return; | |
disposed = true; | |
if (IsRunning) | |
{ | |
TimeKillEvent(timerId); | |
timerId = 0; | |
} | |
if (disposing) | |
{ | |
Elapsed = null; | |
GC.SuppressFinalize(this); | |
} | |
} | |
public event EventHandler Elapsed; | |
~MMTimer() | |
{ | |
Dispose(false); | |
} | |
private static TimerCaps GetTimerCaps() | |
{ | |
var caps = new TimerCaps(); | |
timeGetDevCaps(ref caps, Marshal.SizeOf(caps)); | |
return caps; | |
} | |
[DllImport("ntdll.dll", EntryPoint = "NtSetTimerResolution")] | |
private static extern uint NtSetTimerResolution(uint DesiredResolution, bool SetResolution, ref uint CurrentResolution); | |
public void Start() | |
{ | |
if (disposed) | |
throw new ObjectDisposedException("MMTimer"); | |
if (IsRunning) | |
return; | |
uint userCtx = 0; | |
timerId = TimeSetEvent((uint) Interval, 1, Callback, ref userCtx, EventTypePeriodic); | |
if (timerId == 0) | |
{ | |
var error = Marshal.GetLastWin32Error(); | |
throw new Win32Exception(error); | |
} | |
} | |
public void Stop() | |
{ | |
if (disposed) | |
throw new ObjectDisposedException("MMTimer"); | |
if (!IsRunning) | |
return; | |
TimeKillEvent(timerId); | |
timerId = 0; | |
} | |
[DllImport("winmm.dll")] | |
private static extern int timeGetDevCaps(ref TimerCaps caps, int sizeOfTimerCaps); | |
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeKillEvent")] | |
private static extern void TimeKillEvent(uint uTimerId); | |
private void TimerCallbackMethod(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2) | |
{ | |
var handler = Elapsed; | |
if (handler != null) | |
handler(this, EventArgs.Empty); | |
} | |
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")] | |
private static extern uint TimeSetEvent(uint msDelay, uint msResolution, MultimediaTimerCallback callback, ref uint userCtx, uint eventType); | |
[StructLayout(LayoutKind.Sequential)] | |
private struct TimerCaps | |
{ | |
public readonly int periodMin; | |
public readonly int periodMax; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment