2010-10-13 16 views
10

Scrivo un'app in C#, .NET 3.0 in VS2005 con una funzionalità di monitoraggio dell'inserimento/espulsione di varie unità rimovibili (dischi flash USB, CD-ROM, ecc.). Non volevo usare WMI, dal momento che può essere a volte ambiguo (ad esempio può generare più eventi di inserimento per una singola unità USB), quindi semplicemente sovrascrivo il WndProc della mia mainform per catturare il messaggio WM_DEVICECHANGE, come proposto here. Ieri mi sono imbattuto in un problema quando è risultato che dovrò comunque utilizzare WMI per recuperare alcuni dettagli del disco oscuri come un numero seriale. Si scopre che la chiamata alle routine WMI dall'interno del WndProc genera l'MDA DisconnectedContext.DisconnectedContext MDA quando si chiamano le funzioni WMI nell'applicazione a thread singolo

Dopo alcuni lavori di scavo, ho concluso con una soluzione scomoda per questo. Il codice è il seguente:

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

che sostanzialmente significa che esegue la procedura WMI correlata a un thread separato - ma poi, in attesa che sia completato.

Ora, la domanda è: perché funziona, e perché ha a essere in questo modo? (o, vero?)

Non capisco il fatto di ottenere il DisconnectedContext MDA o RPC_E_WRONG_THREAD in primo luogo. In che modo la procedura GetDrives() da un gestore eventi di clic sui pulsanti differisce dal chiamarla da un WndProc? Non si verificano sullo stesso thread principale della mia app? A proposito, la mia app è completamente single-threaded, quindi perché all'improvviso un errore si riferisce ad un "thread sbagliato"? L'utilizzo di WMI implica il multithreading e il trattamento speciale delle funzioni da System.Management?

Nel frattempo ho trovato un'altra domanda relativa a tale MDA, è here. OK, posso dire che chiamare WMI significa creare un thread separato per il componente COM sottostante - ma ancora non mi viene in mente il motivo per cui la non magia è necessaria quando la si chiama dopo che un pulsante è stato premuto e do-magic è necessario quando si chiama dal WndProc.

Sono davvero confuso e apprezzerei qualche chiarimento in merito. Ci sono solo poche cose peggiori che avere una soluzione e non sapere perché funziona:/

Cheers, Aleksander

+1

Lo stesso problema qui! Vorrei che ci fosse una soluzione. Aggiungerò una taglia ... forse sarà d'aiuto. – Brad

risposta

6

C'è una piuttosto lunga discussione di COM Appartamenti e del messaggio di pompaggio here. Ma il principale punto di interesse è che viene utilizzato il pump dei messaggi per garantire che le chiamate in uno STA vengano correttamente organizzate. Poiché il thread dell'interfaccia utente è lo STA in questione, è necessario pompare i messaggi per garantire che tutto funzioni correttamente.

Il messaggio WM_DEVICECHANGE può essere effettivamente inviato alla finestra più volte. Quindi, nel caso in cui chiami GetDrives direttamente, ti ritrovi effettivamente con le chiamate ricorsive. Inserire un punto di interruzione nella chiamata GetDrives e quindi collegare un dispositivo per attivare l'evento.

La prima volta che si raggiunge il punto di interruzione, tutto a posto. Ora premi F5 per continuare e colpisci il punto di interruzione una seconda volta. Questa volta lo stack di chiamate è qualcosa di simile:

[In un sonno, aspettare, o unirsi] DeleteMeWindowsForms.exe DeleteMeWindowsForms.Form1.WndProc (ref System.Windows.Forms.Message m) Linea 46 C# ! System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.OnMessage (ref System.Windows.Forms.Message m) + 0x13 byte
System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.WndProc (ref System.Windows.Forms.Message m) + 0x31 byte
System.Windows.Forms.dll! System.Windows.Forms.NativeWindow.DebuggableCallback (System.IntPtr hWnd, int msg, System.IntPtr wParam, System.IntPtr lparam) + 0x64 byte [nativo di transizione Managed]
[riusciti a transizione Native]
mscorlib.dll! System.Threading.WaitHandle.InternalWaitOne (System.Runtime .InteropServices.SafeHandle waitableSafeHandle, lungo millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + 0x2b byte mscorlib.dll! System.Threading.WaitHandle.WaitOne (int millisecondsTimeout, bool exitContext) + 0x2D byte
mscorlib.dll! System.Threading. WaitHandle.Wait One() + 0x10 byte System.Management.dll! System.Management.MTAHelper.CreateInMTA (tipo System.Type) + 0x17b byte
System.Management.dll! System.Management.ManagementPath.CreateWbemPath (percorso stringa) + 0x18 byte System.Management.dll! System.Management.ManagementClass.ManagementClass (string path) + 0x29 byte
DeleteMeWindowsForms.exe! DeleteMeWindowsForms.Form1.GetDrives() linea 23 + 0x1b byte C#

Così efficacemente il i messaggi della finestra vengono pompati per garantire che le chiamate COM vengano correttamente eseguite, ma questo ha l'effetto collaterale di chiamare nuovamente WndProc e GetDrives (poiché ci sono messaggi WM_DEVICECHANGE in attesa) in una precedente chiamata GetDrives. Quando si utilizza BeginInvoke, si rimuove questa chiamata ricorsiva.

Ancora una volta, inserire un punto di interruzione nella chiamata GetDrives e premere F5 dopo la prima volta che viene colpito. La prossima volta, aspetta un secondo o due, quindi premi nuovamente F5. A volte fallirà, a volte non lo farà e colpirai di nuovo il tuo punto di interruzione. Questa volta, il tuo callstack includerà tre chiamate a GetDrives, con l'ultimo attivato dall'enumerazione della raccolta diskDriveList. Perché di nuovo, i messaggi vengono pompati per garantire il marshalling delle chiamate.

È difficile individuare esattamente il motivo per cui è stato attivato il MDA, ma date le chiamate ricorsive è ragionevole presumere che il contesto COM possa essere eliminato prematuramente e/o che un oggetto venga raccolto prima che l'oggetto COM sottostante possa essere rilasciato.

+0

Sto lentamente iniziando a capire, quindi portami con me. Fondamentalmente, stai dicendo che la chiamata a GetDrives() richiede che WndProc sul suo modulo sia in esecuzione? Non capisco come questo sia un problema, soprattutto perché permette alla base di gestirlo prima. GetDrives() non verrà chiamato di nuovo, perché sta testando per primo il tipo di messaggio, sì? Puoi approfondire un po 'o indicarmi la giusta direzione? Scusa per la mia confusione. Grazie! – Brad

+0

@Brad - Nessun problema. Se costruisci un campione che usa il codice come sopra, vedrai una traccia di stack simile a quella nella mia risposta. Puoi vedere GetDrives in fondo. Ricorda anche che ho catturato quella traccia dello stack dopo che il mio punto di interruzione sulla chiamata GetDrives è stato colpito. Quindi sta per entrare in un'altra chiamata GetDrives. – CodeNaked

+1

@Brad - Sono stati inviati più messaggi WM_DEVICECHANGE. Quindi la prima volta che si chiama WndProc, gestisce il primo di tali messaggi. La chiamata GetDrives pompa i messaggi per eseguire il marshalling di tutte le chiamate COM nel thread STA (come i valori restituiti dagli oggetti WMI). Poiché ci sono più messaggi WM_DEVICECHANGE in attesa di essere elaborati, il pompaggio della coda dei messaggi li obbligherà a forzare l'override di WndProc. Quindi la ricorsione. – CodeNaked

Problemi correlati