在单线程应用程序中调用WMI函数时,DisconnectedContext MDA

时间:2010-10-13 07:43:53

标签: c# visual-studio-2005 wmi mda

我在VS2005的C#,.NET 3.0中编写了一个应用程序,其功能是监视各种可移动驱动器(USB闪存盘,CD-ROM等)的插入/弹出。我不想使用WMI,因为它有时可能不明确(例如,它可以为单个USB驱动器生成多个插入事件),所以我只是覆盖我的mainform的WndProc以捕获WM_DEVICECHANGE消息,如建议{{3} }。昨天我遇到了一个问题,结果发现我将不得不使用WMI来检索一些模糊的磁盘细节,如序列号。事实证明,从WndProc内部调用WMI例程会抛出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是否意味着System.Management中的多线程和函数的特殊处理?

与此同时,我发现了另一个与该MDA相关的问题,它是here。好吧,我可以认为调用WMI意味着为底层COM组件创建一个单独的线程 - 但是我仍然没有想到为什么在按下按钮后调用它时需要使用no-magic并且在调用时需要do-magic来自WndProc。

我对此感到很困惑,并希望对此事做一些澄清。只有一些比解决方案更糟糕的事情,而不知道它为什么起作用:/

干杯, 亚历山大

1 个答案:

答案 0 :(得分:6)

对COM公寓进行了相当长时间的讨论,并提到了消息here。但主要关注点是消息泵用于确保STA中的呼叫被正确封送。由于UI线程是有问题的STA,因此需要提取消息以确保一切正常。

WM_DEVICECHANGE消息实际上可以多次发送到窗口。因此,在您直接调用GetDrives的情况下,您实际上最终会进行递归调用。在GetDrives调用上设置一个断点,然后附加一个设备来激活该事件。

你第一次达到破发点,一切都很好。现在按F5继续,你将再次击中断点。这次调用堆栈类似于:

  

[在睡觉,等待或加入]       DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Form1.WndProc(ref System.Windows.Forms.Message m)第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       [原产于管理过渡]
      [管理到原生过渡]
      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(字符串路径)+ 0x18字节       System.Management.dll!System.Management.ManagementClass.ManagementClass(字符串路径)+ 0x29字节
      DeleteMeWindowsForms.exe!DeleteMeWindowsForms.Form1.GetDrives()第23行+ 0x1b字节C#

如此有效地抽取窗口消息以确保正确编组COM调用,但这会产生副作用,即在之前的GetDrives调用中再次调用WndProc和GetDrives(因为有待处理的WM_DEVICECHANGE消息)。使用BeginInvoke时,将删除此递归调用。

再次,在GetDrives调用上设置一个断点,并在第一次触发后按F5。下一次,等一两秒再按F5。有时它会失败,有时它会失败,你会再次击中你的断点。这次,您的callstack将包含三次对GetDrives的调用,最后一次调用由diskDriveList集合的枚举触发。因为再一次,消息被泵送以确保调用被编组。

很难准确确定MDA被触发的确切原因,但鉴于递归调用,可以合理地假设COM上下文可能会被过早拆除和/或在基础COM对象被释放之前收集对象