2010-10-13 11 views
10

Piszę aplikację w C#, .NET 3.0 w VS2005 z funkcją monitorowania wstawiania/wyrzucania różnych dysków wymiennych (dysków flash USB, CD-ROM itp.). Nie chciałem korzystać z WMI, ponieważ może to być czasami niejednoznaczne (np. Może zaszczepić wiele zdarzeń wstawienia dla pojedynczego dysku USB), więc po prostu przesłonię WndProc mojego mainformu, aby złapać wiadomość WM_DEVICECHANGE, zgodnie z propozycją here. Wczoraj natknąłem się na problem, gdy okazało się, że będę musiał mimo to korzystać z WMI, aby pobrać trochę niejasne szczegóły dysku, takie jak numer seryjny. Okazuje się, że wywoływanie procedur WMI z wnętrza WndProc powoduje odrzucenie Mconnected DisconnectedContext.DisconnectedContext MDA podczas wywoływania funkcji WMI w aplikacji jednowątkowej

Po pewnym kopaniu skończyłem z niewygodnym obejściem tego problemu. Kod jest następujący:

// 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(); 

co oznacza po prostu uruchomienie procedury związanej z WMI w oddzielnym wątku - ale potem czekanie na jej zakończenie.

Teraz pytanie brzmi: dlaczegoto działa i dlaczegoto ma być w ten sposób? (lub, czy nie?)

Nie rozumiem faktu, że pierwszeństwo otrzymałem od DisconnectedContext MDA lub RPC_E_WRONG_THREAD. W jaki sposób uruchamianie procedury GetDrives() z obsługi zdarzeń kliknięcia przycisku różni się od wywoływania z WndProc? Czy nie dzieje się to w tym samym głównym wątku mojej aplikacji? BTW, moja aplikacja jest całkowicie jednowątkowa, więc dlaczego nagle pojawia się błąd odnoszący się do "niewłaściwej nici"? Czy użycie WMI oznacza wielowątkowość i specjalne traktowanie funkcji od System.Management?

W międzyczasie znalazłem inne pytanie związane z tym MDA, to jest here. OK, mogę przyjąć, że wywołanie WMI oznacza stworzenie oddzielnego wątku dla podstawowego składnika COM - ale nadal nie przychodzi mi do głowy, dlaczego nie jest potrzebna magia, gdy dzwonię po naciśnięciu przycisku, i do-magia jest potrzebna podczas dzwonienia to od WndProc.

Jestem naprawdę zdezorientowany i docenię pewne wyjaśnienia w tej sprawie. Istnieje tylko kilka rzeczy gorsze niż posiadanie rozwiązania, a nie wiedząc, dlaczego to działa:/

Cheers, Aleksander

+1

Same kłopoty tutaj! Szkoda, że ​​nie było rozwiązania. Dodam nagrodę ... może to pomoże. – Brad

Odpowiedz

6

Jest dość długa dyskusja i komunikat COM Apartments pompowania here. Ale głównym celem jest pompa komunikatów, która zapewnia, że ​​połączenia w stacji STA są odpowiednio sterowane. Ponieważ wątek UI to przedmiotowa STA, wiadomości powinny być przepompowane, aby zapewnić, że wszystko działa poprawnie.

Komunikat WM_DEVICECHANGE może zostać przesłany do okna wiele razy. Tak więc w przypadku, gdy wywołasz GetDrives bezpośrednio, skutecznie kończysz się wywołaniami rekurencyjnymi. Umieść punkt przerwania połączenia GetDrives, a następnie podłącz urządzenie, aby wystrzelić zdarzenie.

Po raz pierwszy trafisz w punkt przerwania, wszystko w porządku. Teraz naciśnij F5, aby kontynuować, a drugi raz trafisz w punkt przerwania. Tym razem stos wywołań jest coś takiego:

[W snu, czekaj, lub przyłączyć] DeleteMeWindowsForms.Form1.WndProc (ref System.Windows.Forms.Message m) Linia 46 C# DeleteMeWindowsForms.exe! System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.OnMessage (ref System.Windows.Forms.Message m) + 0x13 bajtów
System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.WndProc (ref System.Windows.Forms.Message m) + 0x31 bajtów
System.Windows.Forms.dll! System.Windows.Forms.NativeWindow.DebuggableCallback (System.IntPtr hWnd int MSG System.IntPtr wParam, System.IntPtr lParam) + 0x64 bajtów [Native zarządzana transformacji]
[Udało się macierzystego stanu]
pliku Mscorlib.dll! System.Threading.WaitHandle.InternalWaitOne (System.Runtime .InteropServices.SafeHandle waitableSafeHandle, długo millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + 0x2b bajtów mscorlib.dll! System.Threading.WaitHandle.WaitOne (int millisecondsTimeout, bool exitContext) + 0x2d bajty
pliku mscorlib.dll! System.Threading. WaitHandle.Wait Jeden() + 0x10 bajtów System.Management.dll! System.Management.MTAHelper.CreateInMTA (typ System.Type) + 0x17b bajtów
System.Management.dll! System.Management.ManagementPath.CreateWbemPath (ciąg znaków) + 0x18 bajty System.Management.ManagementClass.ManagementClass (ścieżka string) System.Management.dll! + 0x29 bajtów
DeleteMeWindowsForms.exe! DeleteMeWindowsForms.Form1.GetDrives() Linia 23 + 0x1b bajtów C#

Tak skutecznie Komunikaty okna są pompowane, aby zapewnić prawidłowe wywoływanie wywołań COM, ale ma to skutek uboczny ponownego wywoływania twoich WndProc i GetDrives (ponieważ oczekują wiadomości WM_DEVICECHANGE) w poprzednim wywołaniu GetDrives. Kiedy używasz BeginInvoke, usuwasz to wywołanie cykliczne.

Ponownie, umieść punkt przerwania w wywołaniu GetDrives i naciśnij klawisz F5 po pierwszym trafieniu. Następnym razem poczekaj sekundę lub dwie, a następnie ponownie naciśnij klawisz F5. Czasami to się nie powiedzie, czasami nie, i znowu trafisz w swój punkt krytyczny. Tym razem twój callstack będzie zawierać trzy wywołania do GetDrives, z których ostatnia zostanie wywołana przez wyliczenie kolekcji diskDriveList. Ponownie, wiadomości są pompowane, aby zapewnić, że połączenia są zbierane.

Trudno dokładnie określić, dlaczego wywoływana jest MDA, ale biorąc pod uwagę rekursywne wywołania, można założyć, że kontekst COM może zostać przedwcześnie zburzony i/lub obiekt zostanie zebrany przed zwolnieniem leżącego pod nim obiektu COM.

+0

Powoli zaczynam rozumieć, więc trzymaj się mnie. Zasadniczo mówisz, że wywołanie GetDrives() wymaga, aby WndProc w jego formularzu działał? Nie rozumiem, jak to jest problemem, zwłaszcza, że ​​najpierw pozwala bazy do obsługi. Funkcja GetDrives() nie zostanie ponownie wywołana, ponieważ najpierw testuje typ wiadomości, tak? Czy możesz rozwinąć nieco więcej lub wskazać mi właściwy kierunek? Przepraszam za moje zamieszanie. Dzięki! – Brad

+0

@Brad - Nie ma problemu. Jeśli zbudujesz próbkę, która używa kodu podobnego do powyższego, zobaczysz podobny ślad stosu do tego w mojej odpowiedzi. Możesz zobaczyć GetDrives jest na dole. Należy również pamiętać, że przechwyciłem ten ślad stosu po tym, jak został złamany mój punkt przerwania połączenia GetDrives. Tak więc ma zamiar przejść do innego wywołania GetDrives. – CodeNaked

+1

@Brad - wysyłanych jest wiele wiadomości WM_DEVICECHANGE. Tak więc po raz pierwszy wywoływany jest WndProc, obsługuje on pierwszą z takich wiadomości. Wywołanie GetDrives pompuje komunikaty w celu przekazywania jakichkolwiek wywołań COM do wątku STA (takich jak wartości zwracane z obiektów WMI). Ponieważ jest więcej komunikatów WM_DEVICECHANGE oczekujących na przetwarzanie, pompowanie kolejki komunikatów zmusi je do przepchnięcia przez nadpisanie WndProc. Zatem rekurencja. – CodeNaked

Powiązane problemy