2010-10-13 36 views
10

我在VS2005的C#,.NET 3.0中編寫了一個應用程序,具有監視插入/彈出各種可移動驅動器(USB閃存盤,CD-ROM等)的功能。我不想使用WMI,因爲它可能有時是不明確的(例如,它可以爲單個USB驅動器產生多個插入事件),所以我簡單地覆蓋了我的mainform的WndProc以捕獲WM_DEVICECHANGE消息,如提議here。昨天我遇到了一個問題,事實證明,我將不得不使用WMI來檢索一些不明確的磁盤細節,如序列號。事實證明,從WndProc內部調用WMI例程會拋出DisconnectedContext MDA。在單線程應用程序中調用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有關的問題,它是here。好吧,我可以認爲調用WMI意味着爲底層COM組件創建一個單獨的線程 - 但它仍然不會出現在爲什麼在按下按鈕後調用它時需要無魔法,並且在調用時需要執行魔法它來自WndProc。

我對此非常困惑,並希望對此事進行一些澄清。只有幾個糟糕的事情比有一個解決方案,不知道爲什麼它的工作原理:/

乾杯, 亞歷山大

+1

同樣的麻煩在這裏!我希望有一個解決方案。我會添加一個賞金......也許這會有所幫助。 – Brad 2011-04-13 23:20:00

回答

6

有COM公寓的抽here一個相當長時間的討論和消息。但感興趣的主要問題是消息泵用於確保STA中的呼叫正確編組。由於UI線程是有問題的STA,因此需要抽取消息以確保一切正常。

WM_DEVICECHANGE消息實際上可以多次發送到窗口。所以在你直接調用GetDrives的情況下,你最終會收到遞歸調用。在GetDrives調用中放置一個斷點,然後附加一個設備來觸發事件。

你第一次打破中斷點,一切都很好。現在按F5繼續,您將再次點擊中斷點。這一次的調用堆棧是一樣的東西:

[休眠,等待,或加入] DeleteMeWindowsForms.exe DeleteMeWindowsForms.Form1.WndProc(REF System.Windows.Forms.Message米)46號線C# ! System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m)+ 0x13 bytes
System.Windows.Forms。dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m)+ 0x31 bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr的hWnd,INT味精,System.IntPtr WPARAM,System.IntPtr LPARAM)+ 0x64字節 [原產於託管過渡]
[託管到純過渡]
mscorlib.dll中!System.Threading.WaitHandle.InternalWaitOne(System.Runtime .InteropServices.SafeHandle waitableSafeHandle,長millisecondsTimeout,布爾hasThreadAffinity,布爾exitContext)+ 0x2B訪問字節 mscorlib.dll中!System.Threading.WaitHandle.WaitOne(INT millisecondsTimeout,布爾exitContext)+ 0x2d字節
mscorlib.dll中!的System.Threading。 WaitHandle.Wait One()+ 0x10 bytes System.Management.dll!System.Management.MTAHelper.CreateInMTA(System.Type type)+ 0x17b bytes
System.Management.dll!System.Management.ManagementPath.CreateWbemPath(string path)+ 0x18字節 System.Management.dll!System.Management.ManagementClass.ManagementClass(字符串路徑)+ 0x29字節
DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Form1.GetDrives()線23 + 0x1b字節C#

所以有效的窗口消息正在被泵送以確保COM調用被正確編組,但是這會產生副作用,即再次調用WndProc和GetDrives(因爲存在掛起的WM_DEVICECHANGE消息),同時仍然在以前的GetDrives調用中。當您使用BeginInvoke時,您將刪除此遞歸調用。

再次,在GetDrives調用上放置一個斷點,並在第一次命中後按F5。下一次,等待一兩秒鐘,然後再次按F5。有時它會失敗,有時不會,你會再次打破你的斷點。這一次,您的調用堆棧將包含三個GetDrives調用,最後一個調用diskDriveList集合的枚舉。因爲再一次,這些消息被抽取以確保呼叫被封送。

很難準確查明爲什麼MDA被觸發,但考慮到遞歸調用是合理的假設COM上下文可以提前拆除和/或一個對象被收集可以釋放潛在的COM對象之前。

+0

我慢慢開始明白,所以忍受着我。基本上,你在說GetDrives()的調用需要在他的窗體上運行WndProc?我不明白這是一個問題,尤其是因爲他允許基地先處理它。 GetDrives()不會再被調用,因爲他首先測試消息類型,是嗎?你可以詳細說明一下,還是指向正確的方向?對不起,我的困惑。謝謝! – Brad 2011-04-15 15:50:43

+0

@Brad - 沒問題。如果您使用上面的代碼構建樣本,則會在我的答案中看到類似的堆棧跟蹤。你可以看到GetDrives在底部。另外請記住,我在獲取GetDrives調用的中斷點後捕獲了該堆棧跟蹤。所以它即將進入另一個GetDrives調用。 – CodeNaked 2011-04-15 15:56:37

+1

@Brad - 正在發送多個WM_DEVICECHANGE消息。所以第一次調用WndProc時,它處理了第一個這樣的消息。 GetDrives調用抽取消息以將任何COM調用編組到STA線程中(例如來自WMI對象的返回值)。由於有更多的WM_DEVICECHANGE消息正在等待處理,所以抽取消息隊列將強制這些消息通過WndProc覆蓋。因此遞歸。 – CodeNaked 2011-04-15 15:58:56

相關問題