2006年03月07日

COM クライアント実装の道程 for TaskScheduler その3

何故か ID3 タグの仕様を調べています。id3v2 には .NET のライブラリとして UltraID3 と言うのが紹介されていて、初めはこれ使えばいいやと思ったのですが、なんとエンコーディングが Unicode と ISO-8859-1 のどちらかしか選べないと言う致命的な問題点があることが判明。これじゃ大抵 Shift_jis 使ってる日本では使えません……。他にこれほどの柔軟性があるのも見当たらないし。

さて、そんなのは置いておいて、COM インターフェイスの実装の続きと参りましょう。

二回連続で脇道に入っていたこともあって些か記憶が曖昧ですが、確か前回は ITaskScheduler インターフェイスの定義とそのメソッド一つの実行に成功したところだったはずです。今回はタスクスケジューラの中心的インターフェイスである、ITask インターフェイスについて話を進めていきましょう。

このインターフェイスの特徴に、IUnknown から直接派生しているわけではなく、IScheduledWorkItem から派生している点があります。.NET において、このインターフェイスの継承というのはどう扱うべきでしょうか?

単純に考えれば、普通に IScheduledWorkItem を定義し、それから派生させて ITask を IScheduledWorkItem との差分メソッドだけ宣言すれば良さそうです。

わざわざ書くだけあって、実際にはそんなわけに行きません。なぜ駄目なのか。その理由が MSDN の エクスポート時の型の変換 のインターフェイスの項目に書かれています。以下ちょっと長めですが引用しましょう。強調は私が付けました。

インターフェイスに InterfaceTypeAttribute 属性を適用することにより、インターフェイスをデュアル インターフェイスとしてエクスポートするか、IUnknown から派生したインターフェイスとしてエクスポートするか、またはディスパッチ専用インターフェイス (Dispinterface) としてエクスポートするかを選択できます。エクスポートされたすべてのインターフェイスは、マネージ コードでのインターフェイスの継承階層に関係なく、すべて IUnknown または IDispatch から直接拡張されます

つまり、CCW (COM Callable Wrapper、COM 呼び出し可能ラッパー)は定義したインターフェイスを COM に公開する際、継承を一切無視して直接 IUnknown から派生させたインターフェイスと扱ってしまうわけです。そうすると当然の事ながら必要なメソッドが定義されていない、不完全なインターフェイスになって、メソッドの呼び出しに失敗することになります。

ではどうするか。いずれにせよ ITask が直接 IUnknown から派生されてしまう以上、ITask に IScheduledWorkItem で定義されたメソッド全てを持たせなければならないのは明らかです。そこで存在が微妙になるのが IScheduledWorkItem です。C# では、IScheduledWorkItem に各種メソッドを宣言し、更に ITask が再宣言する、ということは new キーワードで実現できます。こんな感じですね。

interface IScheduledWorkItem {
    int CreateTrigger();
}
interface ITask : IScheduledWorkItem {
    new int CreateTrigger();
}

ちょっと話が逸れますが、C# においてこのインターフェイスを使う場合、ITask を実装するクラスが明示的実装をしない限り同じメソッドが呼ばれます。では例えばこんな場合はどうでしょう。

class Task : ITask {
    public int CreateTrigger() { return 0; }
    int ITask.CreateTrigger() { return 1; }
}

この Task インスタンスは ITask 、IScheduledWorkItem どちらにもキャストできますが、ではそれぞれの型で CreateTrigger を呼ぶと?

さて、ITask が IScheduledWorkItem のメソッドを再宣言するのが可能なのは分かりました。ではそうするのが一番良いのでしょうか? 改めて ITaskScheduler やその関連のメソッドを見てみると、IScheduledWorkItem でなくてはいけない、ITask では困る、というメソッドが存在しません。取得系は全て IID_ITask を使用するように書いてある以上 ITask が返るのは自明ですし、設定する場合に IScheduledWorkItem を要求されていても、派生関係から ITask を渡して問題はないでしょう。つまり、IScheduledWorkItem は必ずしも存在しなくても構わないということです。であるならば、手は抜ける限り抜くというのが自然な態度ですし、ここは一つ IScheduledWorkItem は無視して、直接 ITask だけ宣言するようにしましょう。

ここでずるずる書いても無駄でしょうから宣言を書き下ろすのは省略。次回以降まとめてコードを提示することにして、さてこのインターフェイスを宣言すると、必要な C 言語スタイルの構造体一つ出てきます。それが SYSTEMTIME 構造体です。意外なことに、ITask で要求される構造体はこれだけです(この後作ることになる ITaskTrigger インターフェイスでは山ほど要求されますけど)。この構造体は ushort(UInt16)のフィールドを年月曜日時分秒ミリ秒の八つもったもので、表現できる最大値は System.DateTime よりも圧倒的に大きいです。65535 年まで扱えますからね(DateTime は 9999 年)。タイマ刻み( 100ナノ秒)単位の DateTime は精密さという点では圧倒的ですが。構造体サイズでも ulong 1つしか持っていない DateTime の方が有利ですね(もっとも、DateTime は何故か StructLayout が Auto で宣言されているのでマーシャリングできません。TimeSpan はマーシャリングできるんですけど)。今回はこの SYSTEMTIME 構造体の考察も行ってみましょう。

ぱっとみて気付くのは、やはりそのフィールドの多さでしょう。まあ BITMAPV5HEADER とかに比べれば可愛いもんですけど。で、C/C++ では当然ながらこういうのはポインタでやりとりします。実体のコピーを行うことはまずありません。さて、そう言う C/C++ の関数と相互運用する場合、C# で定義したこの構造体とのやりとりは通常 ref/out キーワードを使ってポインタにマーシャリングします。このやり方はナチュラルですが、一つ致命的な問題が存在します。NULL ポインタを表現できないことです。そのため NULL ポインタを使用する場合、IntPtr を使ったオーバーロードを宣言する必要があります。コピペで済むので手間はありませんが、コードがごちゃごちゃしますし、IntPtr を付けなければならない引数が二つ出てくるとかなり面倒です。

こういう場合は class として宣言するのが楽です。楽ですが、構造体とごっちゃになると困ります。使い分けははっきりしなければなりません。値のコピーを渡すのか参照のコピーを渡すのかでかなり呼び出し側の意識も変わりますしね。そのため、ここでは Class サフィクスを付けて SystmeTimeClass 型と言うことにします。これならこれが class であること、値渡しでもその値は参照の値であるから呼び出し先で値が変更されうること、の注意を喚起できるでしょう。

SYSTEMTIME 構造体はタスクスケジューラの専売特許ではなく、他の関数でも使用する事が考えられます。ですからここは一つ扱いやすくするべく、各種メソッド・プロパティを用意することにしましょう。でも Add 系のメソッドは、DateTime と違って面倒なので省略。DateTime なら long への足し算一つで済みますが、こっちはそう言うわけにも行きませんからね。そう言うのは DateTime を経由してやって貰うことにしましょう。

まず自然に考えられるのは各パラメータのプロパティ。大小・同一性比較も基本でしょう。そう言えば IComperable インターフェイスなんか欲しいですね。同じ日付表現型と言うことで DateTime への相互変換も基本ですね。あとは現在の値とか最小・最大値とか。ToString は DateTime に委譲すれば良いでしょう。手抜き手抜き。

以上挙げた中で、ちょっと悩むのが比較です。単純に実装するのなら Year から順に比べていけばいいのですが、ここは一つ GetHashCode を利用する方向でいってみましょう。いずれにせよ Equals をオーバーライドするなら GetHashCode もオーバーライドする必要がありますし。ではその GetHashCode をどう実装するか? ushort 8 つを int に押し込めるのは無理です。単純に計算すれば 128 バイト必要と言うことになります。decimal でも収まりませんね。しかしよく考えれば、例えば月は 1 から 12 までしかありません。4 ビット以下で表現しきれます。このように考えていくと、ミリ秒(0-999)で 10 ビット、秒・分(0-59)でそれぞれ 6 ビット、時(0-23)・日(1-31)でそれぞれ 5 ビット、曜日は計算で求まるから不要、月(1-12)で 4 ビット、年(1-65535) で 16 ビット。の合計 52 ビットで表現できると言うことになります。ミリ秒を基準にして秒をその 1000 倍、分を 60000 倍……としていくともうちょっと節約できるでしょうが、計算が面倒ですし節約する意味もありません。long で十分表現できる範囲ですからね。あとはこれを合計してやれば、その日付時刻を唯一表現する long 値が導けます。GetHashCode にはこれの下 32 ビット分を渡すことにして、比較計算ではこの 64 ビット値を使えば単純な数値比較でいけるようになります。

もう一つ考えるべきなのは、値の上限です。ushort を使う関係上、SystemTimeClass では 65535 年まで表現できるわけですが、そうするとその値を DateTime で表現することが不可能になります。そこでここでは相互運用性を考えて、敢えて 9999 年でプログラム的に上限を設けることにします。

相互運用する際に使うものですから、返された値が有り得ない値を示す可能性もあります。例えば 0 で埋められるとか。そのため、現在の値が日付の表現として妥当かどうかを評価するプロパティも用意しておけば親切でしょう。そうでなければ DateTime への変換で問答無用で例外が投げられてしまったりするでしょうからね。

と以上のことを考慮して、SystemTimeClass を以下のように実装します。">XML ドキュメント付きの C# コード も乗せておきましょう。

using System;
using System.Runtime.InteropServices;

namespace HongliangSoft.Utilities.Unmanaged {
  [StructLayout(LayoutKind.Sequential, Pack=2)]
  public class SystemTimeClass : IFormattable, IComparable
#if !V10 && !V11
    , IComparable<SystemTimeClass>, IEquatable<SystemTimeClass>
#endif
  {
    public short Year { get { return this.year; } }
    public short Month { get { return this.month; } }
    public short DayOfWeek { get { return this.dayOfWeek; } }
    public short Day { get { return this.day; } }
    public short Hour { get { return this.hour; } }
    public short Minute { get { return this.minute; } }
    public short Second { get { return this.second; } }
    public short Millisecond { get { return this.millisecond; } }
    public override string ToString() {
      return this.ToDateTime().ToString();
    }
    public string ToString(string format) {
      return this.ToDateTime().ToString(format);
    }
    public string ToString(string format, IFormatProvider provider) {
      return this.ToDateTime().ToString(format, provider);
    }
    public override int GetHashCode() {
      return unchecked((int)this.LocalValue);
    }
    public override bool Equals(object value) {
      return this.Equals(value as SystemTimeClass);
    }
    public bool Equals(SystemTimeClass value) {
      if (value == null)
        return false;
      return this.LocalValue == value.LocalValue;
    }
    // 各パラメータをキャストし合計したlong値。
    private long LocalValue {
      get {
        if (this.IsInvalid) {
          throw new InvalidOperationException(
            "インスタンスが不正なパラメータを含んでいます。");
        }
        long year = this.Year, month = this.Month, day = this.Day;
        long hour = this.Hour, minute = this.Minute;
        long second = this.Second, milli = this.Millisecond;
        return (year << 36) | (month << 32) | (day << 27)
          | (hour << 22) | (minute << 16) | (second << 10) | milli;
      }
    }
    int IComparable.CompareTo(object value) {
      if (value == null)
        return 1;
      SystemTimeClass time = value as SystemTimeClass;
      if (time == null) {
        throw new ArgumentException(
          "オブジェクトは SystemTimeClass 型でなければなりません。",
          "value");
      }
      return this.CompareTo(time);
    }
    public int CompareTo(SystemTimeClass value) {
      if (value == null)
        return 1;
      long diff = this.LocalValue - value.LocalValue;
      return (diff > 0) ? 1 : (diff < 0) ? -1 : 0;
    }
    public static int Compare(SystemTimeClass time1,
                              SystemTimeClass time2) {
      return time1.CompareTo(time2);
    }
    public DateTime ToDateTime() {
      return new DateTime(this.Year, this.Month, this.Day, this.Hour,
                          this.Minute, this.Second, this.Millisecond);
    }
    public static implicit operator DateTime(SystemTimeClass time) {
      return time.ToDateTime();
    }
    public static implicit operator SystemTimeClass(DateTime time) {
      return new SystemTimeClass(time);
    }
    public bool IsInvalid {
      get {
        if (this.year < 1 || this.year > 9999)
          return true;
        if (this.month < 1 || this.month > 12)
          return true;
        if (this.day < 0
            || this.day > DateTime.DaysInMonth(this.year, this.month))
          return true;
        if (this.hour < 0 || this.hour > 23)
          return true;
        if (this.minute < 0 || this.minute > 59)
          return true;
        if (this.second < 0 || this.second > 59)
          return true;
        if (this.millisecond < 0 || this.millisecond > 999)
          return true;
        return false;
      }
    }
    private SystemTimeClass() { }
    public SystemTimeClass(int year, int month, int day)
      : this(year, month, day, 0, 0, 0) { }
    public SystemTimeClass(int year, int month, int day,
                           int hour, int minute, int second)
      : this(year, month, day, hour, minute, second, 0) { }
    public SystemTimeClass(int year, int month, int day,
                           int hour, int minute, int second,
                           int millisecond) {
      if (year < 1 || year > 9999) {
        throw new ArgumentOutOfRangeException(
          "year", year,
          "1 から 9999 までの間でなければなりません。");
      }
      if (month < 1 || month > 12) {
        throw new ArgumentOutOfRangeException(
          "month", month,
          "1 から 12 までの間でなければなりません。");
      }
      if (day < 0 || day > DateTime.DaysInMonth(year, month)) {
        throw new ArgumentOutOfRangeException(
          "day", day,
          "この日付は指定した年・月には存在しません。");
      }
      if (hour < 0 || hour > 23) {
        throw new ArgumentOutOfRangeException(
          "hour", hour,
          "0 から 23 までの間でなければなりません。");
      }
      if (minute < 0 || minute > 59) {
        throw new ArgumentOutOfRangeException(
          "minute", minute,
          "0 から 59 までの間でなければなりません。");
      }
      if (second < 0 || second > 59) {
        throw new ArgumentOutOfRangeException(
          "second", second,
          "0 から 59 までの間でなければなりません。");
      }
      if (millisecond < 0 || millisecond > 999) {
        throw new ArgumentOutOfRangeException(
          "millisecond", millisecond,
          "0 から 999 までの間でなければなりません。");
      }
      this.year = (short)year;
      this.month = (short)month;
      this.day = (short)day;
      this.hour = (short)hour;
      this.minute = (short)minute;
      this.second = (short)second;
      this.millisecond = (short)millisecond;
      this.dayOfWeek = (short)(int)new DateTime(year, month, day).DayOfWeek;
    }
    public SystemTimeClass(DateTime time)
      : this(time.Year, time.Month, time.Day, time.Hour,
             time.Minute, time.Second, time.Millisecond) {
    }
    public static SystemTimeClass Now {
      get { return new SystemTimeClass(DateTime.Now); }
    }
    public static readonly SystemTimeClass MinValue
      = new SystemTimeClass(1, 1, 1);
    public static readonly SystemTimeClass MaxValue
      = new SystemTimeClass(9999, 12, 31, 23, 59, 59, 999);
    private short year;
    private short month;
    private short dayOfWeek;
    private short day;
    private short hour;
    private short minute;
    private short second;
    private short millisecond;
  }
}

今回は同時に VB.NET 版も載っけます。7、7.1、8 でそれぞれ別々のコードになるので、#Const で V に 10 / 11 / 20 のいずれかを定義してあげてください。

Imports System
Imports System.Runtime.InteropServices

Namespace HongliangSoft.Utilities.Unmanaged
  <StructLayout(LayoutKind.Sequential, Pack:=2)> _
  Public Class SystemTimeClass
    Implements IFormattable, IComparable
#If Not(V = 10 OrElse V = 11)
    Implements IComparable(Of SystemTimeClass), _
      IEquatable(Of SystemTimeClass)
#End If

    Public Shared Sub Main()
    End Sub
    Public ReadOnly Property Year() As Short
      Get
        Return Me._year
      End Get
    End Property
    Public ReadOnly Property Month() As Short
      Get
        Return Me._month
      End Get
    End Property
    Public ReadOnly Property DayOfWeek() As Short
      Get
        Return Me._dayOfWeek
      End Get
    End Property
    Public ReadOnly Property Day() As Short
      Get
        Return Me._day
      End Get
    End Property
    Public ReadOnly Property Hour() As Short
      Get
        Return Me._hour
      End Get
    End Property
    Public ReadOnly Property Minute() As Short
      Get
        Return Me._minute
      End Get
    End Property
    Public ReadOnly Property Second() As Short
      Get
        Return Me._second
      End Get
    End Property
    Public ReadOnly Property Millisecond() As Short
      Get
        Return Me._millisecond
      End Get
    End Property

    Public Overloads Overrides Function ToString() As String
      Return Me.ToDateTime().ToString()
    End Function
    Public Overloads Function ToString(ByVal format As String) _
        As String
      Return Me.ToDateTime().ToString(format)
    End Function
    Public Overloads Function ToString( _
        ByVal format As String, ByVal provider As IFormatProvider) _
        As String Implements IFormattable.ToString
      Return Me.ToDateTime().ToString(format, provider)
    End Function
    Public Overrides Function GetHashCode() As Integer
      Return CInt(Me.LocalValue And &HFFFFFFFF)
    End Function
    Public Overloads Overrides Function Equals(ByVal Value As Object) _
        As Boolean
      Return Me.Equals(DirectCast(Value, SystemTimeClass))
    End Function
#If Not(V = 10 OrElse V = 11)
    Public Overloads Function Equals(ByVal Value As SystemTimeClass) _
        As Boolean Implements IEquatable(Of SystemTimeClass).Equals
#Else
    Public Overloads Function Equals(ByVal Value As SystemTimeClass) _
        As Boolean
#End If
      If Value Is Nothing Then
        Return False
      Else
        Return Me.LocalValue.Equals(Value.LocalValue)
      End If
    End Function
    ' 各パラメータをキャストし合計したLong値。
    Private ReadOnly Property LocalValue() As Long
      Get
        If Me.IsInvalid Then
          Throw New InvalidOperationException( _
                      "インスタンスが不正なパラメータを含んでいます。")
        End If
        Dim _year As Long = Me._year, _month As Long = Me._month
        Dim _day As Long = Me._day, _hour As Long = Me._hour
        Dim _minute As Long = Me._minute, _second As Long = Me._second
        Dim _milli As Long = Me._millisecond
#If Not(V = 10)
        Return (_year << 36) Or (_month << 32) Or (_day << 27) Or _
          (_hour << 22) Or (_minute << 16) Or (_second << 10) Or _milli
#Else
        Return (_year * 68719476736) Or (_month * 4294967296) Or _
          (_day * 134217728) Or (_hour * 4194304) Or _
          (_minute * 65536) Or (_second * 1024) Or _milli
#End If
      End Get
    End Property
    Private Overloads Function CompareTo(ByVal Value As Object) _
        As Integer Implements IComparable.CompareTo
      If Value Is Nothing Then
        Return 1
      End If
#If Not(V = 10 OrElse V = 11)
      Dim _time As SystemTimeClass = TryCast(Value, SystemTimeClass)
      If _time Is Nothing Then
        Throw New ArgumentException( _
             "オブジェクトは SystemTimeClass 型でなければなりません。", _
             "value")
      End If
#Else
      If Not(TypeOf Value Is SystemTimeClass) Then
        Throw New ArgumentException( _
             "オブジェクトは SystemTimeClass 型でなければなりません。", _
             "value")
      End If
      Dim _time As SystemTimeClass = DirectCast(Value, SystemTimeClass)
#End If
      Return Me.CompareTo(_time)
    End Function
#If Not(V = 10 OrElse V = 11)
    Public Overloads Function CompareTo(ByVal Value As SystemTimeClass) _
        As Integer Implements IComparable(Of SystemTimeClass).CompareTo
#Else
    Public Overloads Function CompareTo(ByVal Value As SystemTimeClass) _
        As Integer
#End If
      If Value Is Nothing Then
        Return 1
      End If
      Dim diff As Long = Me.LocalValue - Value.LocalValue
      If diff > 0 Then
        Return 1
      ElseIf diff < 0 Then
        Return -1
      Else
        Return 0
      End If
    End Function
    Public Shared Function Compare(ByVal time1 As SystemTimeClass, _
                                   ByVal time2 As SystemTimeClass) _
                               As Integer
      Return time1.CompareTo(time2)
    End Function
    Public Function ToDateTime() As DateTime
      Return New DateTime(Me.Year, Me.Month, Me.Day, _
                          Me.Hour, Me.Minute, Me.Second, Me.Millisecond)
    End Function
#If Not(V = 10 OrElse V = 11)
    Public Shared Widening Operator CType( _
        ByVal _time As SystemTimeClass) As DateTime
      Return _time.ToDateTime()
    End Operator
    Public Shared Widening Operator CType( _
        ByVal _time As DateTime) As SystemTimeClass
      Return New SystemTimeClass(_time)
    End Operator
#End If
    Public ReadOnly Property IsInvalid() As Boolean
      Get
        If _year < 1 OrElse _year > 9999 Then
          Return True
        End If
        If _month < 1 OrElse _month > 12 Then
          Return True
        End If
        If _day < 0 OrElse _
           _day > DateTime.DaysInMonth(_year, _month) Then
          Return True
        End If
        If _hour < 0 OrElse _hour > 23 Then
          Return True
        End If
        If _minute < 0 OrElse _minute > 59 Then
          Return True
        End If
        If _second < 0 OrElse _second > 59 Then
          Return True
        End If
        If _millisecond < 0 OrElse _millisecond > 999 Then
          Return True
        End If
        Return False
      End Get
    End Property
    Private Sub New()
    End Sub
    Public Sub New(ByVal _year As Integer, ByVal _month As Integer, _
                   ByVal _day As Integer)
      Me.New(_year, _month, _day, 0, 0, 0)
    End Sub
    Public Sub New(ByVal _year As Integer, ByVal _month As Integer, _
                   ByVal _day As Integer, ByVal _hour As Integer, _
                   ByVal _minute As Integer, ByVal _second As Integer)
      Me.New(_year, _month, _day, _hour, _minute, _second, 0)
    End Sub
    Public Sub New(ByVal _year As Integer, ByVal _month As Integer, _
                   ByVal _day As Integer, ByVal _hour As Integer, _
                   ByVal _minute As Integer, ByVal _second As Integer, _
                   ByVal _millisecond As Integer)
      If _year < 1 OrElse _year > 9999 Then
        Throw New ArgumentOutOfRangeException( _
                    "_year", _year, _
                    "1 から 9999 までの間でなければなりません。")
      End If
      If _month < 1 OrElse _month > 12 Then
        Throw New ArgumentOutOfRangeException( _
                    "_month", _month, _
                    "1 から 12 までの間でなければなりません。")
      End If
      If _day < 1 OrElse _
          _day > DateTime.DaysInMonth(_year, _month) Then
        Throw New ArgumentOutOfRangeException( _
                    "_day", _day, _
                    "この日付は指定した年・月には存在しません。")
      End If
      If _hour < 0 OrElse _hour > 23 Then
        Throw New ArgumentOutOfRangeException( _
                    "_hour", _hour, _
                    "0 から 23 までの間でなければなりません。")
      End If
      If _minute < 0 OrElse _minute > 59 Then
        Throw New ArgumentOutOfRangeException( _
                    "_minute", _minute, _
                    "0 から 59 までの間でなければなりません。")
      End If
      If _second < 0 OrElse _second > 59 Then
        Throw New ArgumentOutOfRangeException( _
                    "_second", _second, _
                    "0 から 59 までの間でなければなりません。")
      End If
      If _millisecond < 0 OrElse _millisecond > 999 Then
        Throw New ArgumentOutOfRangeException( _
                    "_millisecond", _millisecond, _
                    "0 から 999 までの間でなければなりません。")
      End If
      Me._year = CShort(_year)
      Me._month = CShort(_month)
      Me._day = CShort(_day)
      Me._hour = CShort(_hour)
      Me._minute = CShort(_minute)
      Me._second = CShort(_second)
      Me._millisecond = CShort(_millisecond)
      Me._dayOfWeek = CShort(New DateTime(_year, _month, _day).DayOfWeek)
    End Sub
    Public Sub New(ByVal _time As DateTime)
      Me.New(_time.Year, _time.Month, _time.Day, _
             _time.Hour, _time.Minute, _time.Second, _time.Millisecond)
    End Sub
    Public Shared ReadOnly Property Now() As SystemTimeClass
      Get
        Return New SystemTimeClass(DateTime.Now)
      End Get
    End Property
    Public Shared ReadOnly MinValue As SystemTimeClass = _
        New SystemTimeClass(1, 1, 1)
    Public Shared ReadOnly MaxValue As SystemTimeClass = _
        New SystemTimeClass(9999, 12, 31, 23, 59, 59, 999)
    Private _year As Short
    Private _month As Short
    Private _dayOfWeek As Short
    Private _day As Short
    Private _hour As Short
    Private _minute As Short
    Private _second As Short
    Private _millisecond As Short
  End Class
End Namespace

さて、今回はここまで。次回は ITaskTrigger と、new できるインターフェイスについてを予定しています。



posted by Hongliang at 05:33| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は180日以上新しい記事の投稿がないブログに表示されております。