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

前端之家收集整理的这篇文章主要介绍了c# – 在单线程应用程序中调用WMI函数时,DisconnectedContext MDA前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我在VS2005中的C#.NET 3.0中编写了一个应用程序,具有监控各种可移动驱动器(USB闪存盘,CD-ROM等)的插入/弹出功能.我不想使用WMI,因为它可能有时是模糊的(例如,它可以为单个USB驱动器产生多个插入事件),所以我只是覆盖我的主窗体的WndProc来捕获WM_DEVICECHANGE消息,如建议 here.昨天我遇到问题,原来我将不得不使用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中调用它不同?它们不是发生在我应用程序的同一主线上吗? BTW,我的应用程序是完全单线程的,所以为什么所有的突然一个错误指的是一些“错误的线程”?使用WMI是否意味着来自System.Management的多线程和功能的特殊处理?

在此期间,我发现了另一个与该MDA相关的问题,它是here.可以,我可以认为,调用WMI意味着为基础COM组件创建一个单独的线程,但是我仍然不会发生,为什么不需要魔术在按下按钮后调用它,并在从WndProc调用它时需要执行魔术.

我真的很困惑,对此感到有些澄清.只有几个更糟糕的事情,而不是解决方案,而不知道为什么它的工作原理:

干杯,
克瓦希

解决方法

有一个相当长的讨论COM公寓和消息抽取 here.但主要的兴趣是消息泵是用来确保STA中的呼叫被正确的封送.由于UI线程是有问题的STA,所以需要抽取消息来确保一切正常.

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对象.

猜你在找的C#相关文章