DisconnectedContext MDA при вызове функций WMI в однопоточном приложении
Я пишу приложение на C#, .NET 3.0 в VS2005 с функцией мониторинга вставки / выброса различных съемных дисков(USB-флеш-дисков, CD-ROM и т. д.). Я не хотел использовать WMI, так как он может быть иногда неоднозначным (например, он может порождать несколько событий вставки для одного USB-накопителя), поэтому я просто переопределяю WndProc моей mainform, чтобы поймать сообщение WM_DEVICECHANGE, как предложено здесь. Вчера я столкнулся с проблемой, когда оказалось, что мне придется использовать WMI в любом случае, чтобы получить некоторые неясные детали диска, такие как серийный номер. Оказывается, вызов процедур WMI изнутри WndProc вызывает DisconnectedContext MDA.
после некоторого рытья я закончил с неудобным обходным путем для этого. Код выглядит следующим образом:
// 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 процедуры в отдельном потоке, но затем, ожидая ее завершения.
теперь вопрос: почему это работает, и почему это должно быть так? (или нет?)
Я не понимаю факта получения DisconnectedContext MDA или RPC_E_WRONG_THREAD в первую очередь. Как работает GetDrives()
процедура из обработчика событий нажатия кнопки отличается от вызова его из WndProc? Разве они не происходят в одном и том же основном потоке моего приложения? Кстати, мое приложение полностью однопоточное, так почему вдруг возникла ошибка со ссылкой на какой-то "неправильный поток"? Ли применение WMI означает multithreading и специальная обработка функций от системы.Управление?
тем временем я нашел еще один вопрос, связанный с этим MDA, это здесь. Хорошо, я могу считать, что вызов WMI означает создание отдельного потока для базового компонента COM - но мне все еще не приходит в голову, почему не требуется магия при вызове его после нажатия кнопки и do-magic требуется при вызове его из WndProc.
Я действительно смущен этим и буду признателен за некоторые разъяснения по этому вопросу. Есть только несколько худших вещей, чем иметь решение и не знать, почему оно работает :/
Ура, Александр!--5-->
1 ответов
существует довольно долгое обсуждение com квартир и сообщений накачки здесь. Но основной интерес представляет то, что насос сообщений используется для обеспечения правильного маршалирования вызовов в STA. Поскольку поток пользовательского интерфейса является STA, о котором идет речь, сообщения должны быть перекачаны, чтобы убедиться, что все работает правильно.
сообщение WM_DEVICECHANGE может быть отправлено в окно несколько раз. Поэтому в случае, когда вы вызываете GetDrives напрямую, вы эффективно в конечном итоге с рекурсивными вызовами. Поместите точку останова в вызов GetDrives, а затем присоедините устройство для запуска события.
в первый раз вы попали в точку разрыва, все в порядке. Теперь нажмите F5, чтобы продолжить, и вы нажмете точку останова во второй раз. На этот раз стек вызовов выглядит примерно так:
[во сне, ждать, или присоединиться] DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Форма form1.Функция WndProc(система Реф.Окна.Формы.Сообщение m) строка 46 C# Система.Окна.Формы.dll файлы!Система.Окна.Формы.Управление.ControlNativeWindow.OnMessage (ref System.Окна.Формы.Сообщение m) + 0x13 байт
Система.Окна.Формы.dll файлы!Система.Окна.Формы.Управление.ControlNativeWindow.Функция WndProc(система Реф.Окна.Формы.Сообщение m) + 0x31 байт
Система.Окна.Формы.dll файлы!Система.Окна.Формы.NativeWindow.DebuggableCallback (System.Указателя IntPtr hwnd элемента, инт МСГ системы.Указателя IntPtr параметр wparam, системы.IntPtr lparam) + 0x64 байт [Родной для управляемого Переход]
[Удалось родной переход]
библиотеку mscorlib.dll файлы!Система.Нарезка резьбы.Объекта waithandle.InternalWaitOne (System.Во время выполнения.InteropServices.SafeHandle waitableSafeHandle, длинные миллисекунды, bool hasThreadAffinity, bool exitContext) + 0x2b байт библиотеку mscorlib.dll файлы!Система.Нарезка резьбы.Объекта waithandle.Метод waitone(инт параметр millisecondstimeout, exitContext боол) + 0x2d байт
библиотеку mscorlib.dll файлы!Система.Нарезка резьбы.Объекта waithandle.WaitOne () + 0x10 байт Система.Управление.dll файлы!Система.Управление.Мтахелпер.CreateInMTA (Система.Тип типа) + 0x17b байт
Система.Управление.dll файлы!Система.Управление.ManagementPath.CreateWbemPath (строковый путь) + 0x18 байт Система.Управление.dll файлы!Система.Управление.ManagementClass.ManagementClass(string путь) + 0x29 байт
DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Форма form1.GetDrives () строка 23 + 0x1b байт C#
так эффективно сообщения окна перекачиваются в убедитесь, что вызовы COM правильно маршалированы, но это имеет побочный эффект вызова WndProc и GetDrives снова (поскольку есть ожидающие сообщения WM_DEVICECHANGE), в то же время в предыдущем вызове GetDrives. При использовании BeginInvoke этот рекурсивный вызов удаляется.
снова поставьте точку останова на вызов GetDrives и нажмите F5 после первого нажатия. В следующий раз, подождите секунду или две, затем снова нажмите клавишу F5. Иногда это потерпит неудачу, иногда нет, и ты ударишь. опять останова. На этот раз ваш callstack будет включать три вызова GetDrives, причем последний вызов вызывается перечислением коллекции diskDriveList. Потому что снова сообщения перекачиваются, чтобы гарантировать, что вызовы маршалируются.
трудно точно определить, почему MDA запускается, но с учетом рекурсивных вызовов разумно предположить, что контекст COM может быть сорван преждевременно и / или объект собирается до того, как базовый COM-объект может быть освобожденным.