Monday, March 9, 2009

High Resolution Timer via Interop

Though Windows isn’t a real-time OS, we do need high resolution clocks and timers sometimes. .Net gives us Stopwatch class which is really a high resolution clock. But we can’t find a high resolution timer in .Net framework. A timer is different from a clock, which fires event periodically notifying you the period of time is up. This article is mainly to demonstrate the basic usage of interop. And a more complex and intensive material provided at the end of the article.

There are high resolution timer services on Windows. But they are native APIs. Fortunately, we can use interop to call these native APIs.

The first thing you have to do is to determine the device capabilities. Different devices have different resolutions[1]. Some may give you 1 ms of resolution, but some may be 10 ms. And if you request a resolution that the device can’t support, an error will occur. To determine the resolution of the timer hardware, use timeGetTime().

[DllImport("winmm.dll")]
private static extern int timeGetDevCaps(ref TimerCaps caps, int sizeOfTimerCaps);
TimerCaps is a struct. A C struct actually. We can use C# struct to map the data.
[StructLayout(LayoutKind.Sequential)]
public struct TimerCaps
{
/// <summary>
/// Minimum supported period in milliseconds.
/// </summary>
public uint periodMin;
/// <summary>
/// Maximum supported period in milliseconds.
/// </summary>
public uint periodMax;
}

Please keep in mind that “LayoutKind.Sequential” is essential, because it suppressed C# compiler from alter the order of the fields for optimization purpose. Here for the parameter “TimerCaps caps” we use “ref”, because we want the API to write some data into “caps”. The “ref” maps the type to the pointer of the type in C/C++. So “ref TimerCaps caps” means “TimerCaps *caps” in C/C++.

Now we can call the API.

TimerCaps caps = new TimerCaps();
int ret = timeGetDevCaps(ref caps, Marshal.SizeOf(caps));

And then we can start the timer.

[DllImport("winmm.dll")]
private static extern int timeSetEvent(uint delay, uint resolution, MmTimerCallback proc, int user, int mode);
...
actuallPeriod = Math.Max(caps.periodMin, Period);
timeBeginPeriod(actuallPeriod);
timerId = timeSetEvent(1000/*delay*/, 0/*resolution*/, TimerCallback, 0, 1);

“MmTimerCallback” is a delegate method mapping to callback function in C/C++. That’s almost the complete stuffs of the high resolution timer. Please be aware, the callback function will be called in a thread other than your main thread. You have to delegate the operation to your main thread if you want to access UI elements.

Here is a good read material talks about interop.