トップ  >  サンプルソース  >  ガベージコレクションとデリゲート(C#/VB.NET)

ガベージコレクションとデリゲート(C#/VB.NET)

2010年10月29日

"ガベージコレクションされたデリゲート"と言う例外が発生する事があります。原因はアンマネージド(Win32Api等)の引数にデリゲート(関数のアドレス)を渡した後に、そのデリゲートがガベージコレクションされて無くなってしまったからです。

通常、ガベージコレクションはすぐには発生しないので最初は正常に動作していますが、突然、この例外が発生して動かなくなる事があります。このサンプルでは、いつ発生するか分からないガベージコレクションをSystem.GC.Collect()で強制発生させています。

さて、デリゲートがガベージコレクションされないようにするためには如何すれば良いか?ですが、答えは、デリゲート(関数のアドレス)の参照を持っている変数を用意する事です。ガベージコレクションは参照が無くなったインスタンスを整理(削除)するので、まだ使っている事をアピールして置く必要があります。

このサンプル(ソース/コード)では「(コード1)例外が発生する」と「(コード2)例外が発生しない」を比較して記述しています。

' -----------------------------------------------------------
' ガベージコレクションされたデリゲート(VB.NET/VS2005)
Private Const GWL_WNDPROC = -4
Private Declare Function GetWindowLong Lib "user32.dll" _
    Alias "GetWindowLongA" ( _
    ByVal hwnd As Integer, _
    ByVal nIndex As Integer) As Integer
Private Declare Function SetWindowLong Lib "user32.dll" _
    Alias "SetWindowLongA" ( _
    ByVal hwnd As Integer, _
    ByVal nIndex As Integer, _
    ByVal dwNewLong As Integer) As Integer
Private Declare Function SetWindowLong Lib "user32.dll" _
    Alias "SetWindowLongA" ( _
    ByVal hwnd As Integer, _
    ByVal nIndex As Integer, _
    ByVal dwNewLong As D_MyWndProc) As Integer
Private Declare Function CallWindowProc Lib "user32.dll" _
    Alias "CallWindowProcA" ( _
    ByVal lpPrevWndFunc As Integer, _
    ByVal hwnd As Integer, _
    ByVal msg As Integer, _
    ByVal wParam As Integer, _
    ByVal lParam As Integer) As Integer
Private Declare Function SendMessage Lib "user32.dll" _
    Alias "SendMessageA" ( _
    ByVal hwnd As Integer, _
    ByVal msg As Integer, _
    ByVal wParam As Integer, _
    ByVal lParam As Integer) As Integer

' デフォルトWindowProc
Private lngWnP As Integer

' デリゲート
Private Delegate Function D_MyWndProc( _
    ByVal hwnd As Integer, _
    ByVal msg As Integer, _
    ByVal wParam As Integer, _
    ByVal lParam As Integer) As Integer
Private DgWndProc As D_MyWndProc = Nothing

Private Sub Form1_Load( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles MyBase.Load

    lngWnP = GetWindowLong(Me.Handle, GWL_WNDPROC)

    ' ガベージコレクションされたデリゲートが発生するか?
    ' (コード1)例外が発生する
    Call SetWindowLong( _
        Me.Handle, GWL_WNDPROC, AddressOf MyWndProc)

    ' (コード2)例外が発生しない
    'DgWndProc = AddressOf MyWndProc
    'Call SetWindowLong( _
    '    Me.Handle, GWL_WNDPROC, DgWndProc)
End Sub

Private Sub Form1_FormClosed( _
    ByVal sender As System.Object, _
    ByVal e As System.Windows.Forms.FormClosedEventArgs) _
    Handles MyBase.FormClosed

    Call SetWindowLong(Me.Handle, GWL_WNDPROC, lngWnP)
End Sub

Private Sub Button1_Click( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles Button1.Click

    ' ガベージコレクションの強制実行
    System.GC.Collect()
End Sub

Private Sub Timer1_Tick( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles Timer1.Tick

    SendMessage(Me.Handle, 9999, 0, 0)
    ' コールバックが、型 'Form1+D_MyWndProc::Invoke' の
    ' ガベージ コレクションされたデリゲートで行われました。
    ' これは、アプリケーションのクラッシュ、破損、
    ' およびデータの損失を発生させる可能性があります。
    ' デリゲートをアンマネージ コードに渡すとき、
    ' デリゲートは 2 度と呼び出されないことが確実になるまで
    ' マネージ アプリケーションによって維持されなければなり
    ' ません。
End Sub

Private Function MyWndProc( _
    ByVal hwnd As Integer, _
    ByVal msg As Integer, _
    ByVal wParam As Integer, _
    ByVal lParam As Integer) As Integer

    Select Case msg
        Case 9999
            System.Diagnostics.Debug.WriteLine( _
                Now & "メッセージを受信しました")
    End Select
    Return CallWindowProc(lngWnP, hwnd, msg, wParam, lParam)
End Function
' -----------------------------------------------------------

// ----------------------------------------------------------
// ガベージコレクションされたデリゲート(C#.NET/VS2005)
private static int GWL_WNDPROC = -4;
[System.Runtime.InteropServices.DllImport
    ("user32.dll", EntryPoint = "GetWindowLongA")]
    extern static int GetWindowLong
    (int hwnd, int nIndex);
[System.Runtime.InteropServices.DllImport
   ("user32.dll", EntryPoint = "SetWindowLongA")]
    extern static int SetWindowLong
    (int hwnd, int nIndex, int dwNewLong);
[System.Runtime.InteropServices.DllImport
    ("user32.dll", EntryPoint = "SetWindowLongA")]
    extern static int SetWindowLong
    (int hwnd, int nIndex, D_MyWndProc dwNewLong);
[System.Runtime.InteropServices.DllImport
    ("user32.dll", EntryPoint = "CallWindowProcA")]
    extern static int CallWindowProc
    (int lpPrevWndFunc, int hwnd, int msg,
    int wParam, int lParam);
[System.Runtime.InteropServices.DllImport
    ("user32.dll", EntryPoint = "SendMessageA")]
    extern static int SendMessage(
    int hwnd, int msg, int wParam, int lParam);

// デフォルトWindowProc
private static int lngWnP;

// デリゲート
private delegate int D_MyWndProc(
    int hwnd, int msg, int wParam, int lParam);
private D_MyWndProc DgWndProc = null;

private void Form1_Load(
    object sender, EventArgs e)
{
    lngWnP = GetWindowLong((int)this.Handle, GWL_WNDPROC);

    // ガベージコレクションされたデリゲートが発生するか?
    // (コード1)例外が発生する
    SetWindowLong((int)this.Handle, GWL_WNDPROC, MyWndProc);

    // (コード2)例外が発生しない
    //DgWndProc = MyWndProc;
    //SetWindowLong((int)this.Handle, GWL_WNDPROC, DgWndProc);
}

private void Form1_FormClosed(
    object sender, FormClosedEventArgs e)
{
    SetWindowLong((int)this.Handle, GWL_WNDPROC, lngWnP);
}

private void button1_Click(object sender, EventArgs e)
{
    // ガベージコレクションの強制実行
    System.GC.Collect();
}

private void timer1_Tick(object sender, EventArgs e)
{
    SendMessage((int)this.Handle, 9999, 0, 0);
    // コールバックが、型 'Form1+D_MyWndProc::Invoke' の
    // ガベージ コレクションされたデリゲートで行われました。
    // これは、アプリケーションのクラッシュ、破損、
    // およびデータの損失を発生させる可能性があります。
    // デリゲートをアンマネージ コードに渡すとき、
    // デリゲートは 2 度と呼び出されないことが確実になるまで
    // マネージ アプリケーションによって維持されなければなり
    // ません。
}

private int MyWndProc(
    int hwnd, int msg, int wParam, int lParam)
{
    switch (msg)
    {
        case 9999:
            System.Diagnostics.Debug.WriteLine(
                DateTime.Now + "メッセージを受信しました");
            break;
    }
    return CallWindowProc(lngWnP, hwnd, msg, wParam, lParam);
}
// ----------------------------------------------------------
スポンサーサイト

VirtualStoreの悲劇(ファイル操作) | トップページへ戻る | 有効ではないスレッド間の操作(C#/VB.NET)

このページのトップに戻る

コメント

名前
題名
メールアドレス
WEBサイト
 
コメント
パスワード
  管理者にだけ表示を許可する

このページのトップに戻る

トラックバック

このページのトップに戻る