timeSetEvent Windows APIを利用した高精度タイマーコンポーネントの作成 - C#

高精度のタイマー処理をコンポーネント化して手軽に扱えるようにしたコードを紹介します。

コンポーネント部コード

以下のコードを記述します。
iPentecEventTimer.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace iPentecEventTimer
{
  public partial class iPentecEventTimer : Component
  {
    public delegate void OnTimerDelegate();
    private OnTimerDelegate onTimer;

    private CRITICAL_SECTION CriticalSection;
    private uint TimerID = 0;
    TimerEventHandler teHandler;

    bool _enabled = false;

    public uint Resolution { set; get; }
    public uint Interval { set; get; }
    public bool Enabled
    {
      set
      {
        _enabled = value;
        UpdateTimer();
      }
      get
      {
        return _enabled;
      }
    }
    

    public iPentecEventTimer()
    {
      InitializeComponent();
      InitalizeProperty();
    }
    
    public iPentecEventTimer(IContainer container)
    {
      container.Add(this);
      InitializeComponent();
      InitalizeProperty();
    }

    private void InitalizeProperty(){
      Resolution = 100;
      Interval = 1000;
      Enabled = false;

      teHandler += TimerProc;
    }

    private void UpdateTimer()
    {
      if (DesignMode == false) {

        if (Enabled == true) {
          if (TimerID != 0) {
            timeKillEvent(TimerID);
          }

          if (timeBeginPeriod(Resolution) == TIMERR_NOERROR) {
            uint userctx = 0;
            TimerID = timeSetEvent(Interval, Resolution, teHandler, ref userctx, TIME_PERIODIC);
          }
        
        }
        else {
          if (TimerID != 0) {
            timeKillEvent(TimerID);
            timeEndPeriod(Resolution);
          }
        }
      }
    }

    private void TimerProc(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2)
    {
      
      InitializeCriticalSection(out CriticalSection);
      EnterCriticalSection(ref CriticalSection);
      try {
        if (onTimer != null) {
          onTimer();
        }
      }
      finally {
        LeaveCriticalSection(ref CriticalSection);
      }

       
    }

    //OnTimer Event
    public event OnTimerDelegate OnTimer
    {
      add
      {
        onTimer += value;
      }
      remove
      {
        onTimer -= value;
      }
    }
  }
}
iPentecEventTimerDefine.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace iPentecEventTimer
{
  public partial class iPentecEventTimer : Component 
  {
    [DllImport("winmm.dll", SetLastError = true)]
    static extern UInt32 timeSetEvent(UInt32 msDelay, UInt32 msResolution, TimerEventHandler handler, ref UInt32 userCtx, UInt32 eventType);

    [DllImport("winmm.dll", SetLastError = true)]
    static extern UInt32 timeKillEvent(UInt32 timerEventId);

    [DllImport("winmm.dll")]
    public static extern uint timeBeginPeriod(uint uMilliseconds);

    [DllImport("winmm.dll")]
    public static extern uint timeEndPeriod(uint uMilliseconds);

    [DllImport("kernel32.dll")]
    static extern void InitializeCriticalSection(out CRITICAL_SECTION lpCriticalSection);
    //[DllImport("kernel32.dll")]
    //static extern void InitializeCriticalSection(IntPtr lpCriticalSection);

    [DllImport("kernel32.dll")]
    static extern void EnterCriticalSection(ref CRITICAL_SECTION lpCriticalSection);
    //[DllImport("kernel32.dll")]
    //static extern void EnterCriticalSection(IntPtr lpCriticalSection);

    [DllImport("kernel32.dll")]
    static extern void LeaveCriticalSection(ref CRITICAL_SECTION lpCriticalSection);
    //[DllImport("kernel32.dll")]
    //static extern void LeaveCriticalSection(IntPtr lpCriticalSection);

    [StructLayout(LayoutKind.Sequential)]
    public struct CRITICAL_SECTION{
      public IntPtr DebugInfo;
      public long LockCount;
      public long RecursionCount;
      public uint OwningThread;
      public uint LockSemaphore;
      public int Reserved;
    }

    const int TIMERR_BASE = 96;
    const int TIMERR_NOERROR = 0;
    const int TIMERR_NOCANDO = TIMERR_BASE + 1;
    const int TIMERR_STRUCT = TIMERR_BASE + 33;

    const uint TIME_ONESHOT = 0;
    const uint TIME_PERIODIC = 1;
    const uint TIME_CALLBACK_FUNCTION = 0x0000;
    const uint TIME_CALLBACK_EVENT_SET = 0x0010;
    const uint TIME_CALLBACK_EVENT_PULSE = 0x0020;

    private delegate void TimerEventHandler(UInt32 id, UInt32 msg, ref UInt32 userCtx, UInt32 rsv1, UInt32 rsv2);
  }
}

動作

動作の概要はこちらの記事を参照してください。

コンポーネント利用アプリ プログラム

UI

下図のUIを作成します。


タイマーコンポーネントはツールパレットに表示されるので、フォームに配置します。(下図参照)
また、タイマーコンポーネントのOnTimerイベントにコードを記述します。

コード

下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace iPentecEventTimerDemo
{
  public partial class FormMain : Form
  {
    int cnt = 0;
    delegate void Proc();
    Proc dp;

    public FormMain()
    {
      InitializeComponent();
      dp = showMessge;

    }

    private void button1_Click(object sender, EventArgs e)
    {
      iPentecEventTimer1.Enabled = true;
    }

    private void iPentecEventTimer1_OnTimer()
    {
      Invoke(dp);
      cnt++;
    }

    private void showMessge(){
      textBox1.Text += string.Format("+ : {0:d}\r\n", cnt);
    }

    private void button2_Click(object sender, EventArgs e)
    {
      iPentecEventTimer1.Enabled = false;
    }
  }
}

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[Start]ボタンをクリックします。タイマーが開始され、1秒に1行ずつメッセージがテキストボックスに表示されます。



[Stop]ボタンをクリックします。タイマーが停止し、メッセージの表示が停止します。


著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2014-05-26
iPentec all rights reserverd.