Question

J'écris une application en C#, .NET 3.0 dans VS2005 avec une fonctionnalité 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 être parfois ambigu (par ex.cela peut générer plusieurs événements d'insertion pour une seule clé USB), je remplace donc simplement le WndProc de mon formulaire principal pour intercepter le message WM_DEVICECHANGE, comme proposé ici.Hier, j'ai rencontré un problème lorsqu'il s'est avéré que je devais de toute façon utiliser WMI pour récupérer des détails obscurs sur le disque, comme un numéro de série.Il s'avère que l'appel de routines WMI depuis l'intérieur du WndProc génère le MDA DisconnectedContext.

Après quelques recherches, j'ai fini par trouver une solution de contournement délicate à ce problème.Le code est comme suit:

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

ce qui signifie essentiellement exécuter la procédure liée à WMI sur un thread séparé - mais ensuite attendre qu'elle se termine.

Maintenant, la question est : pourquoi est-ce que ça marche, et pourquoi est-ce que ça doit être comme ça ?(ou est-ce que c'est le cas ?)

Je ne comprends pas le fait d'obtenir le MDA DisconnectedContext ou RPC_E_WRONG_THREAD en premier lieu.Comment fonctionne la course GetDrives() La procédure à partir d'un gestionnaire d'événements de clic de bouton diffère de son appel à partir d'un WndProc ?Ne se produisent-ils pas sur le même fil de discussion principal de mon application ?BTW, mon application est entièrement monothread, alors pourquoi tout d'un coup une erreur faisant référence à un « mauvais thread » ?L’utilisation de WMI implique-t-elle du multithreading et un traitement particulier des fonctions de System.Management ?

En attendant, j'ai trouvé une autre question liée à ce MDA, c'est ici.OK, je peux supposer qu'appeler WMI signifie créer un thread séparé pour le composant COM sous-jacent - mais je ne comprends toujours pas pourquoi aucune magie n'est nécessaire lors de son appel après avoir appuyé sur un bouton et do-magic est nécessaire lors de l'appel. il depuis le WndProc.

Je suis vraiment confus à ce sujet et j'apprécierais des éclaircissements à ce sujet.Il n'y a rien de pire que d'avoir une solution et de ne pas savoir pourquoi elle fonctionne :/

Bravo, Aleksander

Était-ce utile?

La solution

Il y a une discussion assez longue sur COM Apartments et le pompage de messages ici.Mais le principal point intéressant est que la pompe à messages est utilisée pour garantir que les appels dans une STA sont correctement organisés.Étant donné que le thread de l'interface utilisateur est le STA en question, les messages devraient être pompés pour garantir que tout fonctionne correctement.

Le message WM_DEVICECHANGE peut en fait être envoyé plusieurs fois à la fenêtre.Ainsi, dans le cas où vous appelez directement GetDrives, vous vous retrouvez 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.Appuyez maintenant sur F5 pour continuer et vous atteindrez le point d'arrêt une seconde fois.Cette fois, la pile d'appels ressemble à ceci :

Dans un sommeil, attendre ou joindre] DeleteMewindowsForms.exe! DeleteMewindowsForms.form1.wndproc (ref System.Windows.Forms.Message M) Ligne 46 C # System.Windows.Forms.dll! .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 [natif de transition gérée
[Transition gérée vers native]
mscorlib.dll!System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + 0x2b bytes mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext ) + 0x2d octets
mscorlib.dll ! System.Threading.WaitHandle.WaitOne() + 0x10 octets System.Management.dll ! System.Management.MTAHelper.CreateInMTA(type System.Type) + 0x17b octets
System.Management.dll ! System.Management.ManagementPath.CreateWbemPath(chemin d’accès à la chaîne) + 0x18 octets System.Management.dll ! System.Management.ManagementClass.ManagementClass(chemin d’accès à la chaîne) + 0x29 octets
DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Form1.GetDrives() Ligne 23 + 0x1b octets C#

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

Encore une fois, placez un point d'arrêt sur l'appel GetDrives et appuyez sur F5 après la première fois qu'il est atteint.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 pile d'appels comprendra trois appels à GetDrives, le dernier étant déclenché par l'énumération de la collection diskDriveList.Car encore une fois, les messages sont pompés pour garantir que les appels sont organisés.

Il est difficile de déterminer exactement pourquoi 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é.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top