10

VS2005 の C#、.NET 3.0 で、さまざまなリムーバブル ドライブ (USB フラッシュ ディスク、CD-ROM など) の挿入/排出を監視する機能を備えたアプリを作成します。WMI はあいまいな場合があるため (たとえば、単一の USB ドライブに対して複数の挿入イベントが発生する可能性があるため)、WMI を使用したくなかったため、ここで提案されているように、メインフォームの WndProc をオーバーライドして WM_DEVICECHANGE メッセージをキャッチするだけです。昨日、シリアル番号のような不明瞭なディスクの詳細を取得するためにとにかく WMI を使用する必要があることが判明したとき、問題に遭遇しました。WndProc 内から WMI ルーチンを呼び出すと、DisconnectedContext MDA がスローされることが判明しました。

掘り下げた後、私はそのための厄介な回避策で終わりました。コードは次のとおりです。

    // the function for calling WMI 
    private void GetDrives()
    {
        ManagementClass diskDriveClass = new ManagementClass("Win32_DiskDrive");
        // THIS is the line I get DisconnectedContext MDA on when it happens:
        ManagementObjectCollection diskDriveList = diskDriveClass.GetInstances();
        foreach (ManagementObject dsk in diskDriveList)
        {
            // ...
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        // here it works perfectly fine
        GetDrives();
    }


    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        if (m.Msg == WM_DEVICECHANGE)
        {
            // here it throws DisconnectedContext MDA 
            // (or RPC_E_WRONG_THREAD if MDA disabled)
            // GetDrives();
            // so the workaround:
            DelegateGetDrives gdi = new DelegateGetDrives(GetDrives);
            IAsyncResult result = gdi.BeginInvoke(null, "");
            gdi.EndInvoke(result);
        }
    }
    // for the workaround only
    public delegate void DelegateGetDrives();

これは基本的に、WMI 関連の手順を別のスレッドで実行することを意味しますが、完了するまで待機します。

さて、問題は、なぜそれが機能するのか、そしてなぜそのようにしなければならないのかということです. (または、そうですか?)

そもそも DisconnectedContext MDA または RPC_E_WRONG_THREAD を取得するという事実がわかりません。GetDrives()ボタン クリック イベント ハンドラーからのプロシージャの実行は、WndProc からの呼び出しとどのように異なりますか? それらはアプリの同じメイン スレッドで発生しませんか? ところで、私のアプリは完全にシングル スレッドです。WMI の使用は、System.Management からの関数のマルチスレッド化と特別な処理を意味しますか?

その間に、その MDA に関連する別の質問を見つけました。ここにあります。わかりました。WMI を呼び出すということは、基礎となる COM コンポーネント用に別のスレッドを作成することを意味すると理解できます。 WndProc から。

私はそれについて本当に混乱しており、その問題についていくつかの説明をいただければ幸いです。解決策を持っていて、それが機能する理由がわからないことよりも悪いことはいくつかあります:/

乾杯、アレクサンダー

4

1 に答える 1

6

ここでは、COMアパートとメッセージポンピングについてかなり長い議論があります。ただし、主な関心事は、STA内のコールが適切にマーシャリングされるようにするためにメッセージポンプが使用されることです。UIスレッドは問題のSTAであるため、すべてが正しく機能することを確認するためにメッセージをポンピングする必要があります。

WM_DEVICECHANGEメッセージは、実際にはウィンドウに複数回送信できます。したがって、GetDrivesを直接呼び出す場合は、事実上再帰呼び出しになります。GetDrives呼び出しにブレークポイントを設定してから、イベントを発生させるデバイスを接続します。

初めてブレークポイントに到達したときは、すべて問題ありません。次にF5を押して続行すると、もう一度ブレークポイントに到達します。今回のコールスタックは次のようなものです。

[スリープ中、待機中、または参加中] DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Form1.WndProc(ref System.Windows.Forms.Message m)46行目C#System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow .OnMessage(ref System.Windows.Forms.Message m)+ 0x13バイト
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m)+0x31バイト
システム.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd、int msg、System.IntPtr wparam、System.IntPtr lparam)+0x64バイト[ネイティブからマネージドへの移行]
[マネージドからネイティブへの移行]
mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle、long mmolsTimeout、bool hasThreadAffinity、bool exitContext)+ 0x2bバイトmscorlib.dll!System.Threading.WaitHandle.WaitOne(int mmolsTimeout、bool exitContext )+ 0x2dバイト
mscorlib.dll!System.Threading.WaitHandle.WaitOne()+ 0x10バイトSystem.Management.dll!System.Management.MTAHelper.CreateInMTA(System.Type type)+ 0x17bバイト
System.Management.dll!System。 Management.ManagementPath.CreateWbemPath(文字列パス)+ 0x18バイトSystem.Management.dll!System.Management.ManagementClass.ManagementClass(文字列パス)+ 0x29バイト
DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Form1.GetDrives()23行目+ 0x1bバイトC#

したがって、COM呼び出しが適切にマーシャリングされるように、ウィンドウメッセージが効果的にポンピングされますが、これには、前のGetDrives呼び出し中に、WndProcおよびGetDrivesを再度呼び出すという副作用があります(保留中のWM_DEVICECHANGEメッセージがあるため)。BeginInvokeを使用すると、この再帰呼び出しが削除されます。

ここでも、GetDrives呼び出しにブレークポイントを設定し、最初にヒットした後にF5キーを押します。次回は、1〜2秒待ってから、もう一度F5キーを押します。失敗することもあれば失敗することもあり、ブレークポイントに再び到達します。今回は、コールスタックにGetDrivesへの3つの呼び出しが含まれ、最後の呼び出しはdiskDriveListコレクションの列挙によってトリガーされます。繰り返しになりますが、メッセージは、呼び出しがマーシャリングされることを保証するためにポンピングされます。

MDAがトリガーされる理由を正確に特定することは困難ですが、再帰呼び出しを考えると、COMコンテキストが時期尚早に破棄されたり、基になるCOMオブジェクトが解放される前にオブジェクトが収集されたりする可能性があると考えるのが妥当です。

于 2011-04-14T02:02:34.093 に答える