tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板) [ツリー表示へ]   [Home]
一括表示(VB.NET VB2005)
タイトルクリップボード監視時のエラーについて
記事No8866
投稿日: 2009/04/16(Thu) 14:10
投稿者take
いつもお世話になります。

Windows XP SP3
Visua lBasic Express Edition
VB初心者です。

クリップボードを監視するソフトを作り始めたのですが、下記エラーが出て止まってしまいます。
”コールバックが、型 'test!test.Form1+SubClassProcDelegate::Invoke' のガベージ コレクションされたデリゲートで行われました。これは、アプリケーションのクラッシュ、破損、およびデータの損失を発生させる可能性があります。デリゲートをアンマネージ コードに渡すとき、デリゲートは 2 度と呼び出されないことが確実になるまでマネージ アプリケーションによって維持されなければなりません。”
エラーはフォームをアクティブにしたり、しなかったりを繰り返すと出ます。
ガベージコレクションが行われる時に落ちると思い、試しにコードにGC.Collect()を入れたところ落ちなくなりました。
下記がテストで作ったコードです。フォームにLabelだけ貼り付けてあります。
GC.Collect()をコメントアウトすると、しつこく操作しているうちに落ちます。
どこに問題があるのでしょうか、私には力不足でわかりません。
どなたかご教示お願いできませんでしょうか。

Public Class Form1

  'クリップボード内容変更通知取得設定(WM_DRAWCLIPBOARDメッセージを受け取ります)
  Declare Function SetClipboardViewer Lib "user32" (ByVal hwnd As Integer) As Integer
  'クリップボード内容変更通知解除
  Declare Function ChangeClipboardChain Lib "user32" (ByVal hwnd As Integer, ByVal hWndNext As Integer) As Integer
  'サブクラス化用
  Delegate Function SubClassProcDelegate(ByVal hwnd As Integer, ByVal msg As Integer, ByVal wParam As Integer, _
                                         ByVal lParam As Integer) As Integer
  Declare Function SetWindowLong1 Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer, _
                                                                      ByVal lVal As SubClassProcDelegate) As Integer
  Declare Function SetWindowLong2 Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer, _
                                                                      ByVal dwNewLong As Integer) As Integer
  '定数
  Public Const GWL_WNDPROC = (-4) 'ウインドウプロシージャ
  Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Integer, ByVal hwnd As Integer, _
                                                                        ByVal Msg As Integer, ByVal wParam As Integer, _
                                                                        ByVal lParam As Integer) As Integer
  Public Const WM_DRAWCLIPBOARD = &H308   'クリップボードの内容が変更された時

  Public P_hwndNext As Integer

  Public Function WindowProc_ClipGet(ByVal hwnd As Integer, ByVal uMsg As Integer, _
                                   ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    Try
      Static Check As Boolean
      If Not Check Then
        Check = True
        Select Case uMsg
          Case WM_DRAWCLIPBOARD 'クリップボードが変更された
            Me.Label1.Text = Clipboard.GetText.ToString
            '**********************************
            '*   こうすると落ちない なぜ?   *
            '**********************************
            GC.Collect()

        End Select
        Check = False
      End If

    Catch ex As Exception
      MsgBox(ex.Message & " WindowProc")
    End Try

    WindowProc_ClipGet = CallWindowProc(P_hwndNext, hwnd, uMsg, wParam, lParam)

  End Function

  'サブクラス化
  Public Sub SubClass(ByVal hwnd As Integer)
    Try
      If P_hwndNext = 0 Then
        P_hwndNext = SetWindowLong1(hwnd, GWL_WNDPROC, AddressOf WindowProc_ClipGet)
      End If
    Catch ex As Exception
      MsgBox(ex.Message & " SubClass")
    End Try
  End Sub

  'サブクラス化終了
  Public Sub UnSubClass(ByVal hwnd As Integer)
    Try
      Dim ret As Integer
      If P_hwndNext <> 0 Then
        '元のプロシージャアドレスに設定する
        ret = SetWindowLong2(hwnd, GWL_WNDPROC, P_hwndNext)
        P_hwndNext = 0
      End If
    Catch ex As Exception
      MsgBox(ex.Message & " UnSubClass")
    End Try
  End Sub

  Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
    ChangeClipboardChain(Me.Handle, P_hwndNext) 'クリップボードの監視終了
    UnSubClass(Me.Handle) 'サブクラス化終了
  End Sub


  Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Me.TopMost = True
    P_hwndNext = SetClipboardViewer(Me.Handle) 'クリップボードの監視スタート
    SubClass(Me.Handle) 'サブクラス化
  End Sub

End Class

[ツリー表示へ]
タイトルRe: クリップボード監視時のエラーについて
記事No8867
投稿日: 2009/04/16(Thu) 15:56
投稿者Hongliang
まず、いわゆるサブクラス化は、WinForms では継承と WndProc のオーバーライドで対応します。WndProc で Web 検索すれば色々見つかるでしょう。
これで問題そのものが消えますね。

さて今回の問題の原因ですが。
AddressOf 演算子は Delegate オブジェクトを作って返します。この Delegate オブジェクトへの参照が残っている限り、GC によって片付けられる心配はありません。
しかし、SetWindowLong はメモリ管理外、つまりアンマネージドな世界です。この関数にオブジェクトを渡したところで、そっちに参照が確保されたりはしません。
そうすると、AddressOf で作成した Delegate オブジェクトは、メソッドを抜ければ誰からも参照されないただの使い捨てのローカル変数と言うことになります。
参照が残らないのでそのうち GC が動いたときに片付けられ、その後ウィンドウプロシージャがコールバックされたときに参照すべきメソッドのあて先が消失していて例外、と言うわけです。
GC.Collect の呼び出しによってっていうのはジェネレーションが上がったからとかその程度の理由で、解決策にはなり得ていません。
デリゲート変数をフィールドに確保して、AddressOf の返値をそれに代入してやれば、参照はそのオブジェクトの寿命まで(この場合なら Form1 ですね。名前からしてメインフォームでしょうから事実上アプリケーションの終了まで)残ることになって GC の対象にならなくなります。

[ツリー表示へ]
タイトルおかげさまで解決しました。
記事No8868
投稿日: 2009/04/16(Thu) 17:06
投稿者take
Hongliang様、ほんとにありがとうございました。
おかげさまで解決しました。

目的を実現するためにネットに載っていることを良く理解せず(というか説明が難しくてすぎて理解できなかったり・・・)に使うこともしばしばです。そのためたまに今回のような理解不能なことが起きてしまいます。

今回のご説明は大変わかり易く、ほんとに勉強になりました。
ありがとうございました。
またよろしくお願いします。

[ツリー表示へ]