いよいよ連載も大詰め。TaskTrigger について解説していきましょう。
その前にまずは大元の TASK_TRIGGER 構造体を解説しなければなりません。TASK_TRIGGER 構造体はこれまで述べたとおりタスクの起動するタイミング(トリガ)を記述する構造体で、この構造体一つであらゆるトリガを表現します。そのため、TASK_TRIGGER は以下のメンバを持ちます。
- 自身がどの種類の起動タイプなのかを表す TASK_TRIGGER_TYPE 列挙体の値
- その起動タイプに固有の値を持つ TRIGGER_TYPE_UNION 共用体
起動タイプというのは、つまりそのトリガが一回だけ有効なトリガなのか、週単位で実行するトリガなのか、ユーザのログオン時だけ発動するトリガなのか、などを表します。また起動タイプに依らない共通のメンバとして以下のものがあります。
- そのトリガの有効期間の開始年月日と終了年月日
- トリガの開始時間。ただし一部のイベント駆動タイプのトリガ起動タイプでは無視されます
- そのトリガで起動したプログラムの持続時間と間隔
- トリガの詳細を記述するための各種フラグ
わざわざ分けて書いたとおり、TASK_TRIGGER の肝は TRIGGER_TYPE_UNION にあると言えます。この共用体は日単位、週単位、月単位、月の週単位、の各起動タイプでそれぞれ別構造体を格納し、TASK_TRIGGER.TASK_TRIGGER_TYPE に応じたものを使用します。
さて、以上がTASK_TRIGGER の概要ですが、これはお世辞にも直感的とは言えません。なにより共用体が邪魔です。そこでここを整理し、直接 TaskTrigger にそれらをまとめてセットする SetAs... メソッドを用意することにします。ユーザは SetAs... メソッドを呼び出すだけで起動タイプを設定できます。このやり方は、ユーザが起動タイプを列挙できないと言うデメリットが存在しますが、いずれにせよ設定するには各起動タイプの詳細を知っている必要があるので問題にはならないでしょう。
また、TASK_TRIGGER が持つ日付や時間間隔のパラメータはいずれも整数値ですが、これらは整理して DateTime/TimeSpan に構成します。
そう言えば今まで共用体を扱ったことが無かった気がするので、今回はその解説に行を費やすことにしましょう。
共用体は、同じバイトデータを違う意味で捉えるための構造体の一種です。例えば 4 バイトのデータがあるとき、これを DWORD 一つとして見たり WORD 二つとして見たりするわけです。今回の題材である TRIGGER_TYPE_UNION は、同じバイトデータを四つの構造体のいずれかとして見ます。MSDN では次のような定義になっています。
typedef union _TRIGGER_TYPE_UNION {
DAILY Daily;
WEEKLY Weekly;
MONTHLYDATE MonthlyDate;
MONTHLYDOW MonthlyDOW;
} TRIGGER_TYPE_UNION;
この四つの構造体はいずれも同じバイトデータを持つことになります。Daily メンバの一バイト目とMonthlyDOW メンバの一バイト目はどちらも TRIGGER_TYPE_UNION の一バイト目です。
今回の TASK_TRIGGER のように起動タイプによって与えるべき構造体が異なる場合、このような共用体を使えば、構造体の利点を持ちつつシンプルに格納することができます。
.NET では型がはっきりしていること、いざとなれば Object 型を使うことができることから共用体の必然性はほぼ無いですが、今回のようにアンマネージドとの相互運用においてはそう言うわけにもいきません。C#/VB.NET では、共用体は構造体の構文を利用して定義します。通常の構造体と違うのは、型宣言部に StructLayout 属性で LayoutKind.Explicit を指定すること、各フィールドに FieldOffset 属性で共用体先頭からのバイトオフセットを明示的に指定することです。例えば TRIGGER_TYPE_UNION なら次のような感じになります。
[StructLayout(LayoutKind.Explicit)]
public struct TriggerTypeUnion {
[FieldOffset(0)] Daily Daily;
[FieldOffset(0)] Weekly Weekly;
[FieldOffset(0)] MonthlyDate MonthlyDate;
[FieldOffset(0)] MonthlyDayOfWeek MonthlyDayOfWeek;
}
TRIGGER_TYPE_UNION は独立した共用体のため FieldOffset が全部 0 なので単純ですね。構造体のメンバとして無名の共用体が混じってたりするとややこしくなりますが。
この共用体は、マーシャリング時だけでなくマネージドコード内や unsafe 構文内でも普通に共用体として機能します。Daily フィールドを設定した後 Weekly フィールドのメンバを見れば、Weekly フィールドに対してはなんの操作もしていないのに値が入っているのが確認できます。
繰り返しますが、.NET では出番はないものです。オーバーロードや継承を使えば解決できるはずのことですから。こんな面倒な定義方法になっているのもそれを強調したいためでしょう。それを念頭に置いて、ご利用は計画的に。
さて、大体解説もこの程度で一通りと言うことになります。次回、多分最終回。TaskScheduler クラスの使用方法と全ソース、の予定です。


制作途上で放置中です。ご期待に添えられず申し訳ないですが。
何か実装が面倒なことに引っかかって疲れたからだったような気がします<放置
何か他にもう一つくらいここにちょろっとだけ書いたけど放置中、なのがある気がするな……。