2010-10-13 17 views
10

J'écris une application en C#, .NET 3.0 dans VS2005 avec une fonction de surveillance de l'insertion/éjection de divers lecteurs amovibles (disques flash USB, CD-ROM, etc.). Je ne voulais pas utiliser WMI, car il peut parfois être ambigu (par exemple, il peut générer plusieurs événements d'insertion pour un seul lecteur USB), donc je remplace simplement le WndProc de ma mainform pour attraper le message WM_DEVICECHANGE, comme proposé here. Hier, j'ai rencontré un problème quand il s'est avéré que je devrais utiliser WMI de toute façon pour récupérer des détails de disque obscurs comme un numéro de série. Il s'avère que l'appel de routines WMI depuis l'intérieur de WndProc lève le MDA DisconnectedContext. Après quelques recherches, je me suis retrouvé avec une solution de contournement maladroite pour cela. Le code est le suivant:DéconnectedContext MDA lors de l'appel de fonctions WMI dans une application monothread

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

qui signifie essentiellement la procédure en cours d'exécution liés à WMI sur un thread séparé - mais, en attendant qu'elle se termine.

Maintenant, la question est: pourquoi ça marche, et pourquoi -t-il à être de cette façon? (ou, le fait?)

Je ne comprends pas le fait d'obtenir le MDA DisconnectedContext ou RPC_E_WRONG_THREAD en premier lieu. Comment exécuter la procédure GetDrives() à partir d'un gestionnaire d'événements de clic de bouton diffère-t-il de l'appel d'un WndProc? Ne se produisent-ils pas sur le même thread principal de mon application? BTW, mon application est complètement mono-thread, alors pourquoi tout d'un coup une erreur se référant à un «mauvais fil»? L'utilisation de WMI implique-t-elle le multithreading et un traitement spécial des fonctions de System.Management?

En attendant j'ai trouvé une autre question liée à ce MDA, c'est here. OK, je peux supposer qu'appeler WMI signifie créer un thread séparé pour le composant COM sous-jacent - mais il ne me vient toujours pas à l'esprit pourquoi aucune magie est nécessaire lorsqu'on l'appelle après avoir appuyé sur un bouton et que do-magic est nécessaire à partir du WndProc.

Je suis vraiment confus à ce sujet et j'apprécierais des éclaircissements à ce sujet. Il y a quelques choses pires que d'avoir une solution et sans savoir pourquoi cela fonctionne:/

Cheers, Aleksander

+1

Même problème ici! J'aimerais qu'il y ait une solution. Je vais ajouter une prime ... peut-être que cela aidera. – Brad

Répondre

6

Il y a une discussion assez longue de COM Appartements et un message de pompage here. Mais le principal point d'intérêt est la pompe de message qui est utilisée pour s'assurer que les appels dans une STA sont correctement marshalés. Étant donné que le thread UI est le STA en question, les messages doivent être pompés pour s'assurer que tout fonctionne correctement.

Le message WM_DEVICECHANGE peut effectivement être envoyé plusieurs fois à la fenêtre. Donc, dans le cas où vous appelez GetDrives directement, vous finissez effectivement avec des appels récursifs. Placez un point d'arrêt sur l'appel GetDrives, puis attachez un périphérique pour déclencher l'événement.

La première fois que vous atteignez le point de rupture, tout va bien. Maintenant, appuyez sur F5 pour continuer et vous atteindrez le point de rupture une seconde fois. Cette fois, la pile d'appel est quelque chose comme:

[Dans un sommeil, attendez, ou rejoindre] DeleteMeWindowsForms.exe DeleteMeWindowsForms.Form1.WndProc (ref System.Windows.Forms.Message m) Ligne 46 C# ! System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.OnMessage (ref System.Windows.Forms.Message m) + 0x13 octets
System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.WndProc (ref System.Windows.Forms.Message m) + 0x31 octets
System.Windows.Forms.dll! System.Windows.Forms.NativeWindow.DebuggableCallback (System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x64 octets [native transition Managed]
[transition Réussi à indigène]
mscorlib.dll! System.Threading.WaitHandle.InternalWaitOne (System.Runtime .InteropServices.SafeHandle waitableSafeHandle, longue millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + 0x2B octets mscorlib.dll! System.Threading.WaitHandle.WaitOne (int millisecondsTimeout, bool exitContext) + 0x2D octets
mscorlib.dll! System.Threading. WaitHandle.Wait Un() + 0x10 octets System.Management.dll! System.Management.MTAHelper.CreateInMTA (type System.Type) + 0x17b octets
System.Management.dll! System.Management.ManagementPath.CreateWbemPath (chemin de chaîne) + 0x18 octets System.Management.dll! System.Management.ManagementClass.ManagementClass (string path) + 0x29 octets
DeleteMeWindowsForms.exe! DeleteMeWindowsForms.Form1.GetDrives() ligne 23 + 0x1b octets C#

donc efficacement la les messages de la fenêtre sont pompés pour s'assurer que les appels COM sont bien organisés, mais cela a pour effet secondaire d'appeler à nouveau WndProc et GetDrives (car il y a des messages WM_DEVICECHANGE en attente) tout en continuant dans un précédent appel GetDrives. Lorsque vous utilisez BeginInvoke, vous supprimez cet appel récursif.

Encore une fois, mettez un point d'arrêt sur l'appel GetDrives et appuyez sur F5 après la première fois qu'il est frappé. La prochaine fois, attendez une seconde ou deux, puis appuyez à nouveau sur F5. Parfois, cela échouera, parfois non, et vous atteindrez à nouveau votre point d'arrêt. Cette fois, votre callstack inclura trois appels à GetDrives, le dernier étant déclenché par l'énumération de la collection diskDriveList. Parce que encore une fois, les messages sont pompés pour s'assurer que les appels sont marshalés. Il est difficile de déterminer exactement pourquoi le MDA est déclenché, mais étant donné les appels récursifs, il est raisonnable de supposer que le contexte COM peut être détruit prématurément et/ou qu'un objet est collecté avant que l'objet COM sous-jacent puisse être libéré.

+0

Je commence lentement à comprendre, alors supportez-moi. Fondamentalement, vous dites que l'appel à GetDrives() nécessite WndProc sur son formulaire pour être en cours d'exécution? Je ne comprends pas comment c'est un problème, d'autant plus qu'il permet à la base de le gérer en premier. GetDrives() ne sera pas appelé à nouveau, car il teste d'abord le type de message, oui? Pouvez-vous élaborer un peu plus, ou me pointer dans la bonne direction? Désolé pour ma confusion. Merci! – Brad

+0

@Brad - Pas de problème. Si vous construisez un exemple utilisant le code ci-dessus, vous verrez une trace de pile similaire à celle de ma réponse. Vous pouvez voir GetDrives est en bas. Souvenez-vous également que j'ai capturé cette trace de pile après que mon point d'arrêt sur l'appel GetDrives a été atteint. Il est donc sur le point d'entrer dans un autre appel GetDrives. – CodeNaked

+1

@Brad - Plusieurs messages WM_DEVICECHANGE sont envoyés. Ainsi, la première fois que WndProc est appelé, il gère le premier de ces messages. L'appel GetDrives pompe les messages afin de rassembler tous les appels COM dans le thread STA (tels que les valeurs de retour à partir des objets WMI). Comme il y a plus de messages WM_DEVICECHANGE en attente de traitement, le pompage de la file d'attente de messages forcera ceux-ci à passer à travers le remplacement WndProc. Ainsi la récursivité. – CodeNaked