// 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中调用它不同?它们不是发生在我应用程序的同一主线上吗? BTW,我的应用程序是完全单线程的,所以为什么所有的突然一个错误指的是一些“错误的线程”?使用WMI是否意味着来自System.Management的多线程和功能的特殊处理?
在此期间,我发现了另一个与该MDA相关的问题,它是here.可以,我可以认为,调用WMI意味着为基础COM组件创建一个单独的线程,但是我仍然不会发生,为什么不需要魔术在按下按钮后调用它,并在从WndProc调用它时需要执行魔术.
我真的很困惑,对此感到有些澄清.只有几个更糟糕的事情,而不是解决方案,而不知道为什么它的工作原理:
干杯,
克瓦希
解决方法
WM_DEVICECHANGE消息实际上可以多次发送到窗口.因此,在直接调用GetDrives的情况下,您有效地终止了递归调用.在GetDrives调用上放置一个断点,然后附加一个设备来触发事件.
你第一次打破断点,一切都很好.现在按F5继续,你会再次打破断点.这次调用堆栈是这样的:
[In a sleep,wait,or join]
DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Form1.WndProc(ref System.Windows.Forms.Message m) Line 46 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) + 0x13 bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0x31 bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd,int msg,System.IntPtr wparam,System.IntPtr lparam) + 0x64 bytes
[Native to Managed Transition]
[Managed to Native Transition]
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 bytes
mscorlib.dll!System.Threading.WaitHandle.WaitOne() + 0x10 bytes
System.Management.dll!System.Management.MTAHelper.CreateInMTA(System.Type type) + 0x17b bytes
System.Management.dll!System.Management.ManagementPath.CreateWbemPath(string path) + 0x18 bytes
System.Management.dll!System.Management.ManagementClass.ManagementClass(string path) + 0x29 bytes
DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Form1.GetDrives() Line 23 + 0x1b bytes C#
因此,窗口消息被有效地抽取以确保COM调用已正确编组,但是这仍然会在以前的GetDrives调用中再次调用您的WndProc和GetDrives(因为还有待处理的WM_DEVICECHANGE消息).当您使用BeginInvoke时,您将删除此递归调用.
再次,在GetDrives调用上放置一个断点,并在第一次被击中后按F5.下次再等一两秒再按F5.有时它会失败,有时它不会,你会再次打破你的断点.这一次,您的呼叫堆栈将包含三次GetDrives调用,最后一次由diskDriveList集合枚举触发.因为再次,消息被泵送以确保呼叫被封送.
很难准确地确定为什么MDA被触发,但是给定递归调用,合理假设COM上下文可能会过早地被破坏,和/或一个对象被收集,然后才能释放底层的COM对象.