I've used this tool and noticed that my Windows Server 2008 R2 Standard has a 15 ms resolution while Windows 8 has a 1 ms resolution timer.
I would prefer to set the Timer Resolution to 1 ms on Windows Server 2008 R2 because I'm running low-latency software on it.
I've found this msdn article, but it doesn't explain how to change the Timer resolution from a C# program. How do I do that?
You can try this:
public static class WinApi
{
/// <summary>TimeBeginPeriod(). See the Windows API documentation for details.</summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("winmm.dll", EntryPoint="timeBeginPeriod", SetLastError=true)]
public static extern uint TimeBeginPeriod(uint uMilliseconds);
/// <summary>TimeEndPeriod(). See the Windows API documentation for details.</summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
[DllImport("winmm.dll", EntryPoint="timeEndPeriod", SetLastError=true)]
public static extern uint TimeEndPeriod(uint uMilliseconds);
}
And use it like this:
WinApi.TimeBeginPeriod(1);
And to go back to how it was:
WinApi.TimeEndPeriod(1);
Better code to implement this would be:
using System;
using System.Runtime.InteropServices;
using System.Threading;
internal sealed class TimePeriod : IDisposable
{
private const string WINMM = "winmm.dll";
private static TIMECAPS timeCapabilities;
private static int inTimePeriod;
private readonly int period;
private int disposed;
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeGetDevCaps(ref TIMECAPS ptc, int cbtc);
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeBeginPeriod(int uPeriod);
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeEndPeriod(int uPeriod);
static TimePeriod()
{
int result = timeGetDevCaps(ref timeCapabilities, Marshal.SizeOf(typeof(TIMECAPS)));
if (result != 0)
{
throw new InvalidOperationException("The request to get time capabilities was not completed because an unexpected error with code " + result + " occured.");
}
}
internal TimePeriod(int period)
{
if (Interlocked.Increment(ref inTimePeriod) != 1)
{
Interlocked.Decrement(ref inTimePeriod);
throw new NotSupportedException("The process is already within a time period. Nested time periods are not supported.");
}
if (period < timeCapabilities.wPeriodMin || period > timeCapabilities.wPeriodMax)
{
throw new ArgumentOutOfRangeException("period", "The request to begin a time period was not completed because the resolution specified is out of range.");
}
int result = timeBeginPeriod(period);
if (result != 0)
{
throw new InvalidOperationException("The request to begin a time period was not completed because an unexpected error with code " + result + " occured.");
}
this.period = period;
}
internal static int MinimumPeriod
{
get
{
return timeCapabilities.wPeriodMin;
}
}
internal static int MaximumPeriod
{
get
{
return timeCapabilities.wPeriodMax;
}
}
internal int Period
{
get
{
if (this.disposed > 0)
{
throw new ObjectDisposedException("The time period instance has been disposed.");
}
return this.period;
}
}
public void Dispose()
{
if (Interlocked.Increment(ref this.disposed) == 1)
{
timeEndPeriod(this.period);
Interlocked.Decrement(ref inTimePeriod);
}
else
{
Interlocked.Decrement(ref this.disposed);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct TIMECAPS
{
internal int wPeriodMin;
internal int wPeriodMax;
}
}
Use it via:
using (new TimePeriod(1))
{
////...
}
Related
I have some operations in my application which rely on short timers. Using the example code below I have timers firing every ~5ms as required.
On an Intel i5 10400H CPU the timings are observed to be off, and the callback occurs after ~15ms (or a multiple of 15). Using the ClockRes sysinternals tool shows that the machine has a system timer resolution of 15ms even when run after the call to timeBeginPeriod(1) made in the code below.
Using https://cms.lucashale.com/timer-resolution/ to set the resolution to the maximum supported value (0.5ms) does not change the behaviour of the example code.
From what I can see the machine is using the Invariant TSC acpi timer, and forcing it to use HPET (with bcdedit /set useplatformclock true and rebooting) did not change the behaviour.
I can't see anything in the CPU documentation or errata that would explain this.
I don't know where the problem lies and if it is something that is fixable on my end, any ideas?
Edit: Having this program (DPC Latency Checker) open results in the timer queue firing when expected, so it's solveable.
Example code:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
using (new TimePeriod(1))
RunTimer();
}
public static void RunTimer()
{
var completionEvent = new ManualResetEvent(false);
var stopwatch = Stopwatch.StartNew();
var i = 0;
var previous = 0L;
using var x = TimerQueue.Default.CreateTimer((s) =>
{
if (i > 100)
completionEvent.Set();
i++;
var now = stopwatch.ElapsedMilliseconds;
var gap = now - previous;
previous = now;
Console.WriteLine($"Gap: {gap}ms");
}, "", 10, 5);
completionEvent.WaitOne();
}
}
public class TimerQueueTimer : IDisposable
{
private TimerQueue MyQueue;
private TimerCallback Callback;
private object UserState;
private IntPtr Handle;
internal TimerQueueTimer(
TimerQueue queue,
TimerCallback cb,
object state,
uint dueTime,
uint period,
TimerQueueTimerFlags flags)
{
MyQueue = queue;
Callback = cb;
UserState = state;
bool rslt = TQTimerWin32.CreateTimerQueueTimer(
out Handle,
MyQueue.Handle,
TimerCallback,
IntPtr.Zero,
dueTime,
period,
flags);
if (!rslt)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error creating timer.");
}
}
~TimerQueueTimer()
{
Dispose(false);
}
public void Change(uint dueTime, uint period)
{
bool rslt = TQTimerWin32.ChangeTimerQueueTimer(MyQueue.Handle, ref Handle, dueTime, period);
if (!rslt)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error changing timer.");
}
}
private void TimerCallback(IntPtr state, bool bExpired)
{
Callback.Invoke(UserState);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private IntPtr completionEventHandle = new IntPtr(-1);
public void Dispose(WaitHandle completionEvent)
{
completionEventHandle = completionEvent.SafeWaitHandle.DangerousGetHandle();
this.Dispose();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
bool rslt = TQTimerWin32.DeleteTimerQueueTimer(MyQueue.Handle,
Handle, completionEventHandle);
if (!rslt)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error deleting timer.");
}
disposed = true;
}
}
}
public class TimerQueue : IDisposable
{
public IntPtr Handle { get; private set; }
public static TimerQueue Default { get; private set; }
static TimerQueue()
{
Default = new TimerQueue(IntPtr.Zero);
}
private TimerQueue(IntPtr handle)
{
Handle = handle;
}
public TimerQueue()
{
Handle = TQTimerWin32.CreateTimerQueue();
if (Handle == IntPtr.Zero)
{
throw new Win32Exception(Marshal.GetLastWin32Error(), "Error creating timer queue.");
}
}
~TimerQueue()
{
Dispose(false);
}
public TimerQueueTimer CreateTimer(
TimerCallback callback,
object state,
uint dueTime,
uint period)
{
return CreateTimer(callback, state, dueTime, period, TimerQueueTimerFlags.ExecuteInPersistentThread);
}
public TimerQueueTimer CreateTimer(
TimerCallback callback,
object state,
uint dueTime,
uint period,
TimerQueueTimerFlags flags)
{
return new TimerQueueTimer(this, callback, state, dueTime, period, flags);
}
private IntPtr CompletionEventHandle = new IntPtr(-1);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(WaitHandle completionEvent)
{
CompletionEventHandle = completionEvent.SafeWaitHandle.DangerousGetHandle();
Dispose();
}
private bool Disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!Disposed)
{
if (Handle != IntPtr.Zero)
{
bool rslt = TQTimerWin32.DeleteTimerQueueEx(Handle, CompletionEventHandle);
if (!rslt)
{
int err = Marshal.GetLastWin32Error();
throw new Win32Exception(err, "Error disposing timer queue");
}
}
Disposed = true;
}
}
}
public enum TimerQueueTimerFlags : uint
{
ExecuteDefault = 0x0000,
ExecuteInTimerThread = 0x0020,
ExecuteInIoThread = 0x0001,
ExecuteInPersistentThread = 0x0080,
ExecuteLongFunction = 0x0010,
ExecuteOnlyOnce = 0x0008,
TransferImpersonation = 0x0100,
}
public delegate void Win32WaitOrTimerCallback(
IntPtr lpParam,
[MarshalAs(UnmanagedType.U1)] bool bTimedOut);
static public class TQTimerWin32
{
[DllImport("kernel32.dll", SetLastError = true)]
public extern static IntPtr CreateTimerQueue();
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool DeleteTimerQueue(IntPtr timerQueue);
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool DeleteTimerQueueEx(IntPtr timerQueue, IntPtr completionEvent);
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool CreateTimerQueueTimer(
out IntPtr newTimer,
IntPtr timerQueue,
Win32WaitOrTimerCallback callback,
IntPtr userState,
uint dueTime,
uint period,
TimerQueueTimerFlags flags);
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool ChangeTimerQueueTimer(
IntPtr timerQueue,
ref IntPtr timer,
uint dueTime,
uint period);
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool DeleteTimerQueueTimer(
IntPtr timerQueue,
IntPtr timer,
IntPtr completionEvent);
}
public sealed class TimePeriod : IDisposable
{
private const string WINMM = "winmm.dll";
private static TIMECAPS timeCapabilities;
private static int inTimePeriod;
private readonly int period;
private int disposed;
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeGetDevCaps(ref TIMECAPS ptc, int cbtc);
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeBeginPeriod(int uPeriod);
[DllImport(WINMM, ExactSpelling = true)]
private static extern int timeEndPeriod(int uPeriod);
static TimePeriod()
{
int result = timeGetDevCaps(ref timeCapabilities, Marshal.SizeOf(typeof(TIMECAPS)));
if (result != 0)
{
throw new InvalidOperationException("The request to get time capabilities was not completed because an unexpected error with code " + result + " occured.");
}
}
internal TimePeriod(int period)
{
if (Interlocked.Increment(ref inTimePeriod) != 1)
{
Interlocked.Decrement(ref inTimePeriod);
throw new NotSupportedException("The process is already within a time period. Nested time periods are not supported.");
}
if (period < timeCapabilities.wPeriodMin || period > timeCapabilities.wPeriodMax)
{
throw new ArgumentOutOfRangeException("period", "The request to begin a time period was not completed because the resolution specified is out of range.");
}
int result = timeBeginPeriod(period);
if (result != 0)
{
throw new InvalidOperationException("The request to begin a time period was not completed because an unexpected error with code " + result + " occured.");
}
this.period = period;
}
internal static int MinimumPeriod
{
get
{
return timeCapabilities.wPeriodMin;
}
}
internal static int MaximumPeriod
{
get
{
return timeCapabilities.wPeriodMax;
}
}
internal int Period
{
get
{
if (this.disposed > 0)
{
throw new ObjectDisposedException("The time period instance has been disposed.");
}
return this.period;
}
}
public void Dispose()
{
if (Interlocked.Increment(ref this.disposed) == 1)
{
timeEndPeriod(this.period);
Interlocked.Decrement(ref inTimePeriod);
}
else
{
Interlocked.Decrement(ref this.disposed);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct TIMECAPS
{
internal int wPeriodMin;
internal int wPeriodMax;
}
}
}
This seem to be an issue with windows 10 2004. I would guess that it has nothing to do with the processor/motherboard.
A possible workaround might be to use a stopwatch and spinwait on a thread. This would be inadvisable for regular consumer applications since it would consume a full thread, but might be feasible if you have full control of the system.
I encountered the exact same problem under Windows 10 2004. Previous versions did not seem to exhibit the same behavior. CreateTimerQueueTimer does not seem to honor timeBeginPeriod anymore and its minimum period seems to be 15ms (good old 15 ms...).
There are a few people complaining about this problem around, but not a lot. (see this forum entry for example.
I do not know if this is a bug introduced in v2004, or a power-saving "feature" that got sneaked past us.
That being said, official documentation never linked TimerQueueTimers and timeBeginPeriod, so if might have been a bug to begin with that they honored the timeBeginPeriod setting.
In any case, I ended up re-implementing a TimerQueue on top of timeBeginPeriod/timeSetEvent to achieve the required timer frequency.
Running into the same problem, I'm using CreateTimerQueueTimer. What still works is timeSetEvent. You'll loose some precision as it's in whole milliseconds, but it's better than nothing.
I am working on a c# windows app and I want to add a feature where the app will shut itself off after 10 minutes of inactivity.
any implementation code will be welcome.
You might need some p-invoke, specifically GetLastInputInfo windows function. It tells you when was the last input (keyboard, mouse) detected for current user.
internal class Program {
private static void Main() {
// don't run timer too often, you just need to detect 10-minutes idle, so running every 5 minutes or so is ok
var timer = new Timer(_ => {
var last = new LASTINPUTINFO();
last.cbSize = (uint)LASTINPUTINFO.SizeOf;
last.dwTime = 0u;
if (GetLastInputInfo(ref last)) {
var idleTime = TimeSpan.FromMilliseconds(Environment.TickCount - last.dwTime);
// Console.WriteLine("Idle time is: {0}", idleTime);
if (idleTime > TimeSpan.FromMinutes(10)) {
// shutdown here
}
}
}, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
Console.ReadKey();
timer.Dispose();
}
[DllImport("user32.dll")]
public static extern bool GetLastInputInfo(ref LASTINPUTINFO info);
[StructLayout(LayoutKind.Sequential)]
public struct LASTINPUTINFO {
public static readonly int SizeOf = Marshal.SizeOf(typeof (LASTINPUTINFO));
[MarshalAs(UnmanagedType.U4)] public UInt32 cbSize;
[MarshalAs(UnmanagedType.U4)] public UInt32 dwTime;
}
}
Is there a high resolution timer that raises an event each time the timer elapses, just like the System.Timer class? I need a high resolution timer to Elapse every ms.
I keep running into posts that explain that the Stopwatch can measure high resolutions, but I don't want to measure time, I want to create an interval of 1 ms.
Is there something in .NET or am I going to write my own high res timer?
There is nothing built into the .NET framework that I am aware of. Windows has a mechanism for high resolution timer events via the Multimedia Timer API. Below is a quick example I whipped up which seems to do the job. There are also seems to be a good example here.
I will note that this API changes system wide settings that can degrade system performance, so buyer beware. For testing purposes, I would recommend keeping track of how often the timer is firing to verify the timing is similar to the device you are trying to simulate. Since windows is not a real-time OS, the load on your system may cause the MM timer be delayed resulting in gaps of 100 ms that contain 100 events in quick succession, rather than 100 events spaced 1 ms apart. Some additional reading on MM timers.
class Program
{
static void Main(string[] args)
{
TestThreadingTimer();
TestMultimediaTimer();
}
private static void TestMultimediaTimer()
{
Stopwatch s = new Stopwatch();
using (var timer = new MultimediaTimer() { Interval = 1 })
{
timer.Elapsed += (o, e) => Console.WriteLine(s.ElapsedMilliseconds);
s.Start();
timer.Start();
Console.ReadKey();
timer.Stop();
}
}
private static void TestThreadingTimer()
{
Stopwatch s = new Stopwatch();
using (var timer = new Timer(o => Console.WriteLine(s.ElapsedMilliseconds), null, 0, 1))
{
s.Start();
Console.ReadKey();
}
}
}
public class MultimediaTimer : IDisposable
{
private bool disposed = false;
private int interval, resolution;
private UInt32 timerId;
// Hold the timer callback to prevent garbage collection.
private readonly MultimediaTimerCallback Callback;
public MultimediaTimer()
{
Callback = new MultimediaTimerCallback(TimerCallbackMethod);
Resolution = 5;
Interval = 10;
}
~MultimediaTimer()
{
Dispose(false);
}
public int Interval
{
get
{
return interval;
}
set
{
CheckDisposed();
if (value < 0)
throw new ArgumentOutOfRangeException("value");
interval = value;
if (Resolution > Interval)
Resolution = value;
}
}
// Note minimum resolution is 0, meaning highest possible resolution.
public int Resolution
{
get
{
return resolution;
}
set
{
CheckDisposed();
if (value < 0)
throw new ArgumentOutOfRangeException("value");
resolution = value;
}
}
public bool IsRunning
{
get { return timerId != 0; }
}
public void Start()
{
CheckDisposed();
if (IsRunning)
throw new InvalidOperationException("Timer is already running");
// Event type = 0, one off event
// Event type = 1, periodic event
UInt32 userCtx = 0;
timerId = NativeMethods.TimeSetEvent((uint)Interval, (uint)Resolution, Callback, ref userCtx, 1);
if (timerId == 0)
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error);
}
}
public void Stop()
{
CheckDisposed();
if (!IsRunning)
throw new InvalidOperationException("Timer has not been started");
StopInternal();
}
private void StopInternal()
{
NativeMethods.TimeKillEvent(timerId);
timerId = 0;
}
public event EventHandler Elapsed;
public void Dispose()
{
Dispose(true);
}
private void TimerCallbackMethod(uint id, uint msg, ref uint userCtx, uint rsv1, uint rsv2)
{
var handler = Elapsed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
private void CheckDisposed()
{
if (disposed)
throw new ObjectDisposedException("MultimediaTimer");
}
private void Dispose(bool disposing)
{
if (disposed)
return;
disposed = true;
if (IsRunning)
{
StopInternal();
}
if (disposing)
{
Elapsed = null;
GC.SuppressFinalize(this);
}
}
}
internal delegate void MultimediaTimerCallback(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2);
internal static class NativeMethods
{
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeSetEvent")]
internal static extern UInt32 TimeSetEvent(UInt32 msDelay, UInt32 msResolution, MultimediaTimerCallback callback, ref UInt32 userCtx, UInt32 eventType);
[DllImport("winmm.dll", SetLastError = true, EntryPoint = "timeKillEvent")]
internal static extern void TimeKillEvent(UInt32 uTimerId);
}
I couldn't get Mike's solution to work and created a basic wrapper around Windows multi media timer based on this codeproject article https://www.codeproject.com/Articles/17474/Timer-surprises-and-how-to-avoid-them
public class WinMMWrapper
{
[DllImport("WinMM.dll", SetLastError = true)]
public static extern uint timeSetEvent(int msDelay, int msResolution,
TimerEventHandler handler, ref int userCtx, int eventType);
public delegate void TimerEventHandler(uint id, uint msg, ref int userCtx,
int rsv1, int rsv2);
public enum TimerEventType
{
OneTime = 0,
Repeating = 1
}
private readonly Action _elapsedAction;
private readonly int _elapsedMs;
private readonly int _resolutionMs;
private readonly TimerEventType _timerEventType;
private readonly TimerEventHandler _timerEventHandler;
public WinMMWrapper(int elapsedMs, int resolutionMs, TimerEventType timerEventType, Action elapsedAction)
{
_elapsedMs = elapsedMs;
_resolutionMs = resolutionMs;
_timerEventType = timerEventType;
_elapsedAction = elapsedAction;
_timerEventHandler = TickHandler;
}
public uint StartElapsedTimer()
{
var myData = 1; //dummy data
return timeSetEvent(_elapsedMs, _resolutionMs / 10, _timerEventHandler, ref myData, (int)_timerEventType);
}
private void TickHandler(uint id, uint msg, ref int userctx, int rsv1, int rsv2)
{
_elapsedAction();
}
}
Here's an example how to use it
class Program
{
static void Main(string[] args)
{
var timer = new WinMMWrapper(100, 25, WinMMWrapper.TimerEventType.Repeating, () =>
{
Console.WriteLine($"Timer elapsed {DateTime.UtcNow:o}");
});
timer.StartElapsedTimer();
Console.ReadKey();
}
}
The output looks like this
Update 2021-11-19: add TimerEventHandler class member per chris's comment.
There is an option: use Thread.Sleep(0). Attempt to call Thread.Sleep(1) or employ a System.Threading.Timer would always come down to system timer resolution. Depending on one is probably not the best idea, at the end of the day you app might be just not allowed to call timeBeginPeriod(...) from winmm.dll.
Following code can resolve down to +/- 10ns (0.10ms) on my dev machine (i7q) and could be higher. It would put a solid load on one of your CPU cores pushing its use up to 100%. No actual OS slowdown would happen, the code surrenders most of its CPU time quantum by calling Thread.Sleep as early as possible:
var requiredDelayMs = 0.1;
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
while (true)
{
if (sw.Elapsed.TotalMilliseconds >= requiredDelayMs)
{
// call your timer routine
}
Thread.Sleep(0); // setting at least 1 here would involve a timer which we don't want to
}
For the more comprehensive implementation see my other answer
Precision-Timer.NET
https://github.com/HypsyNZ/Precision-Timer.NET
https://www.nuget.org/packages/PrecisionTimer.NET/
A High Precision .NET timer that doesn't kill your CPU or get Garbage Collected.
Its designed to be as easy to use as any other .NET timer.
Try creating new System.Threading.Thread and using System.Threading.Thread.Sleep.
var thrd = new Syatem.Threading.Thread(() => {
while (true) {
// do something
System.Threading.Thread.Sleep(1); // wait 1 ms
}
});
thrd.Start();
I want to use a system wide hotkey in my powershell GUI application. I found this C# code for registering a hotkey and integrated it in my script.
The trouble is, because I can't fully understand the C# code, I don't know how to properly add the parameters for the registerhotkey method.
The goal is: on key press SHIFT+ALT+Z execute the code in $hotkeyExecCode.
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$form1.ClientSize = '200, 150'
$cCode = #"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
/// <summary> This class allows you to manage a hotkey </summary>
public class GlobalHotkeys : IDisposable
{
[DllImport( "user32", SetLastError = true )]
[return: MarshalAs( UnmanagedType.Bool )]
public static extern bool RegisterHotKey (IntPtr hwnd, int id, uint fsModifiers, uint vk);
[DllImport( "user32", SetLastError = true )]
public static extern int UnregisterHotKey (IntPtr hwnd, int id);
[DllImport( "kernel32", SetLastError = true )]
public static extern short GlobalAddAtom (string lpString);
[DllImport( "kernel32", SetLastError = true )]
public static extern short GlobalDeleteAtom (short nAtom);
public const int MOD_ALT = 1;
public const int MOD_CONTROL = 2;
public const int MOD_SHIFT = 4;
public const int MOD_WIN = 8;
public const int WM_HOTKEY = 0x312;
public GlobalHotkeys()
{
this.Handle = Process.GetCurrentProcess().Handle;
}
/// <summary>Handle of the current process</summary>
public IntPtr Handle;
/// <summary>The ID for the hotkey</summary>
public short HotkeyID { get; private set; }
/// <summary>Register the hotkey</summary>
public void RegisterGlobalHotKey(int hotkey, int modifiers, IntPtr handle)
{
UnregisterGlobalHotKey();
this.Handle = handle;
RegisterGlobalHotKey(hotkey, modifiers);
}
/// <summary>Register the hotkey</summary>
public void RegisterGlobalHotKey(int hotkey, int modifiers)
{
UnregisterGlobalHotKey();
try
{
// use the GlobalAddAtom API to get a unique ID (as suggested by MSDN)
string atomName = Thread.CurrentThread.ManagedThreadId.ToString("X8") + this.GetType().FullName;
HotkeyID = GlobalAddAtom(atomName);
if (HotkeyID == 0)
throw new Exception("Unable to generate unique hotkey ID. Error: " + Marshal.GetLastWin32Error().ToString());
// register the hotkey, throw if any error
if (!RegisterHotKey(this.Handle, HotkeyID, (uint)modifiers, (uint)hotkey))
throw new Exception("Unable to register hotkey. Error: " + Marshal.GetLastWin32Error().ToString());
}
catch (Exception ex)
{
// clean up if hotkey registration failed
Dispose();
Console.WriteLine(ex);
}
}
/// <summary>Unregister the hotkey</summary>
public void UnregisterGlobalHotKey()
{
if ( this.HotkeyID != 0 )
{
UnregisterHotKey(this.Handle, HotkeyID);
// clean up the atom list
GlobalDeleteAtom(HotkeyID);
HotkeyID = 0;
}
}
public void Dispose()
{
UnregisterGlobalHotKey();
}
}
"#
Add-Type -TypeDefinition $cCode
$hotkeyExecCode = { $form1.WindowState = "normal"}
[GlobalHotkeys]::RegisterHotKey( ???????? ) # <--------- How do i add the parameters here?
$form1.windowstate = "minimized"
$form1.ShowDialog()
I am creating a program that monitors key presses for controlling iTunes globally. It also has a few WinForms (for displaying track information and editing options).
The low-level keyboard hook works great for awhile. If I just start up the program, keyboard hook is set and iTunes opens. Then I open Notepad and can type tons of stuff really fast and every stroke is captured, with at most 30ms being spent in the hook function (and for the most part <10ms). The hook function simply adds the events onto a queue which is processed by another thread. It is running on its own high-priority thread using it's own Application.Run().
However if I start doing things within iTunes (such as a couple of play/pause clicks which generate events in my program) or within the program (like opening the options window) then the hook function stops being called! This can happen even if the keyboard has never been used (e.g. startup, click play and pause a few times in iTunes, then press a key).
The cause of the hook not being called is not due to too much time being spent in the hook function.
When I call UnhookWindowsHookEx it always returns true, regardless if the hook function was still being called or not.
So, what could be the cause?
One idea (although I have no proof or solutions) is that the managed thread is no longer the correct native thread. I use numerous (managed) threads in my program and I have read that a single native thread can run many managed threads and that a managed thread can change which native thread is running it. Is it possible that the hook is still producing messages but sending them to the wrong thread? If this is the case, how can I work around it?
Edit: The hook and callbacks
A slightly stripped done version of my KeyMonitor. It is stripped down for clarity. I have removed some utilities (like most of the values of the Key enum and many functions of the Keys class like ToString() and FromString()) along with some error handling.
Most of the important stuff is in the KeyMonitor class. KeyMonitor.Start() starts a thread for the messages, KeyMonitor.HookThread() is that thread and creates the hook along with an Application.Run() for the message loop, KeyMonitor.KeyboardHookProc() is the callback function, and KeyMonitor.HookEventDispatchThread() is what dispatches events recorded by the callback.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace KeyTest
{
enum Key : int
{
Shift = 0x10, Ctrl, Alt,
Left_Win = 0x5B, Right_Win,
Left_Shift = 0xA0, Right_Shift, Left_Ctrl, Right_Ctrl, Left_Alt, Right_Alt,
}
class Keys
{
[DllImport("user32.dll")]
private static extern int GetKeyboardState(byte[] pbKeyState);
public const int Count = 256; // vkCode are from 1 to 254, but GetKeyboardState uses 0-255
private readonly bool[] keys = new bool[Count];
public Keys() { }
private void DoModifier(Key x, Key l, Key r) { keys[(int)x] = keys[(int)l] || keys[(int)r]; }
private void DoModifiers()
{
DoModifier(Key.Shift, Key.Left_Shift, Key.Right_Shift);
DoModifier(Key.Ctrl, Key.Left_Ctrl, Key.Right_Ctrl);
DoModifier(Key.Alt, Key.Left_Alt, Key.Right_Alt);
}
private void DoModifier(Key x, Key l, Key r, Key k) { if (k == l || k == r) keys[(int)x] = keys[(int)l] || keys[(int)r]; }
private void DoModifiers(Key k)
{
DoModifier(Key.Shift, Key.Left_Shift, Key.Right_Shift, k);
DoModifier(Key.Ctrl, Key.Left_Ctrl, Key.Right_Ctrl, k);
DoModifier(Key.Alt, Key.Left_Alt, Key.Right_Alt, k);
}
public bool this[int i] { get { return this.keys[i]; } set { this.keys[i] = value; DoModifiers((Key)i); } }
public bool this[Key k] { get { return this.keys[(int)k]; } set { this.keys[(int)k] = value; DoModifiers(k); } }
public void LoadCurrentState()
{
byte[] keyState = new byte[Count];
if (GetKeyboardState(keyState) != 0)
for (int i = 0; i < Count; ++i)
keys[i] = (keyState[i] & 0x80) != 0;
DoModifiers();
}
}
static class KeyMonitor
{
#region Windows API
private delegate int HookProc(int nCode, UIntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll")]
private static extern int CallNextHookEx(int idHook, int nCode, UIntPtr wParam, IntPtr lParam);
private const int WH_KEYBOARD_LL = 13;
private readonly static UIntPtr WM_KEYDOWN = new UIntPtr(0x100), WM_SYSKEYDOWN = new UIntPtr(0x104);
#endregion
public static event KeyEventHandler OverridingKeyChange;
public static event KeyEventHandler KeyChange;
private struct KeyEventData { public int vk; public bool down; }
private static int hook = 0;
private static Thread dispatchThread = null, hookThread = null;
private static Keys keys = new Keys();
private static Queue<KeyEventData> queue = new Queue<KeyEventData>();
private static void Enqueue(int vk, bool down)
{
lock (queue)
{
queue.Enqueue(new KeyEventData() { vk = vk, down = down });
Monitor.Pulse(queue);
}
}
public static Keys Keys { get { return keys; } }
public static void Start()
{
if (hook == 0)
{
dispatchThread = new Thread(HookEventDispatchThread);
hookThread = new Thread(HookThread);
hookThread.Priority = ThreadPriority.Highest;
dispatchThread.Start();
hookThread.Start();
}
}
public static void Stop()
{
if (hook != 0)
{
// Minimal cleanup...
UnhookWindowsHookEx(hook);
Application.Exit();
dispatchThread.Interrupt();
}
}
private static void HookThread()
{
hook = SetWindowsHookEx(WH_KEYBOARD_LL, new HookProc(KeyboardHookProc), IntPtr.Zero, 0);
if (hook == 0) { /* Handle error */ }
keys.LoadCurrentState();
Application.Run();
}
private static int KeyboardHookProc(int nCode, UIntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
Enqueue(Marshal.ReadInt32(lParam), wParam == WM_SYSKEYDOWN || wParam == WM_KEYDOWN);
return CallNextHookEx(hook, nCode, wParam, lParam);
}
private static void HookEventDispatchThread()
{
for (; ; )
{
KeyEventData data;
lock (queue)
{
if (queue.Count == 0)
try
{
Monitor.Wait(queue);
}
catch (ThreadInterruptedException) { return; }
data = queue.Dequeue();
}
if (data.vk == -1)
{
// Done!
keys = new Keys();
queue.Clear();
return;
}
else if (keys[data.vk] == data.down)
continue;
keys[data.vk] = data.down;
KeyEventArgs e = new KeyEventArgs((System.Windows.Forms.Keys)data.vk);
if (OverridingKeyChange != null) OverridingKeyChange(null, e);
if (!e.Handled && KeyChange != null) KeyChange(null, e);
}
}
}
}
You need to save the delegate to a variable that will survive for the duration of your application. Otherwise, delegate is garbage-collected (strange the app did not crash!).
static HookProc hookProc;
...
hookProc = new HookProc(KeyboardHookProc);
hook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, IntPtr.Zero, 0);