DisconnectedContext MDA при вызове функций WMI в однопоточном приложении - PullRequest
10 голосов
/ 13 октября 2010

Я пишу приложение на C #, .NET 3.0 на VS2005 с функцией контроля за вставкой / извлечением различных съемных дисков (флеш-накопителей USB, CD-ROM и т. Д.). Я не хотел использовать WMI, поскольку он иногда может быть неоднозначным (например, он может порождать несколько событий вставки для одного USB-накопителя), поэтому я просто перезаписываю WndProc моей основной формы, чтобы перехватить сообщение WM_DEVICECHANGE, как предложено здесь . Вчера я столкнулся с проблемой, когда выяснилось, что мне все равно придется использовать WMI для получения некоторых неясных деталей диска, таких как серийный номер. Оказывается, что вызов подпрограмм WMI изнутри WndProc выбрасывает MDA DisconnectedContext.

После некоторых копаний я закончил с неуклюжим обходным путем. Код выглядит следующим образом:

    // 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 процедуры в отдельном потоке, но затем ожидание ее завершения.

Теперь возникает вопрос: почему это работает, а почему так должно быть? (или нет?)

Я не понимаю, как получить MDA DisconnectedContext или RPC_E_WRONG_THREAD. Чем запуск процедуры GetDrives() из обработчика событий нажатия кнопки отличается от вызова ее из WndProc? Разве они не происходят в одной и той же основной ветке моего приложения? Кстати, мое приложение полностью однопоточное, так почему же вдруг возникла ошибка, относящаяся к какому-то «неправильному потоку»? Означает ли использование WMI многопоточность и особую обработку функций из System.Management?

Тем временем я нашел еще один вопрос, связанный с этим MDA, он здесь . Хорошо, я могу предположить, что вызов WMI означает создание отдельного потока для базового COM-компонента - но мне все еще не приходит в голову, почему не требуется никакой магии при вызове ее после нажатия кнопки и необходимости делать магию при вызове это из WndProc.

Я действительно смущен этим и был бы признателен за разъяснения по этому вопросу. Есть только несколько худших вещей, чем иметь решение и не знать, почему оно работает: /

Cheers, Александр

1 Ответ

6 голосов
/ 14 апреля 2011

Здесь довольно долго обсуждается COM Apartments и прокачка сообщений здесь .Но основной интерес представляет то, что насос сообщений используется для обеспечения правильного маршалинга вызовов в STA.Поскольку поток пользовательского интерфейса является рассматриваемой STA, сообщения должны быть закачаны, чтобы гарантировать, что все работает правильно.

Сообщение WM_DEVICECHANGE может фактически отправляться в окно несколько раз.Таким образом, в случае, когда вы вызываете GetDrives напрямую, вы фактически получаете рекурсивные вызовы.Установите точку останова на вызов GetDrives, а затем подключите устройство для запуска события.

При первом достижении точки останова все в порядке.Теперь нажмите F5, чтобы продолжить, и вы достигнете точки останова во второй раз.На этот раз стек вызовов выглядит примерно так:

[В режиме ожидания, ожидания или присоединения] DeleteMeWindowsForms.exe! DeleteMeWindowsForms.Form1.WndProc (ref System.Windows.Forms.Message m) Строка 46C # System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.OnMessage (ref System.Windows.Forms.Message m) + 0x13 байт
System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.WndProc (ref System.Windows.Forms.Message m) + 0x31 байт
System.Windows.Forms.dll! System.Windows.Forms.NativeWindow.DebuggableCallback (System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x64 байта [Собственный к управляемому переходу]
[Управляемый к собственному переходу]
mscorlib.dll! System.Threading.WaitHandle.InternalWaitOne (System.Runtime.InteropSerlele waitSafele waitSavices.afele, длинные миллисекундыTimeout, bool hasThreadAffinity, bool exitContext) + 0x2b байт mscorlib.dll! System.Threading.WaitHandle.WaitOne (int millisecondsTimeout, bool exitContext) + 0x2d байт
mscorlib.dll! System.Threading.WaitHandle.WaitOne () + 0x10 байт System.Management.dll! System.Management.MTAHelper.CreateInMTA (тип System.Type) + 0x17b байт
система.Management.dll! System.Management.ManagementPath.CreateWbemPath (путь строки) + 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 может быть освобожден.

...