我在VS2005中的C#,.NET 3.0中编写一个应用程序,其功能是监视各种可移动驱动器(USB闪存磁盘,CD-ROMS等)的插入/弹射。我不想使用WMI,因为有时可能是模棱两可的(例如,它可以为单个USB驱动器产生多个插入事件),因此我只需覆盖Mainform的WNDProc即可捕获WM_DEVICECHANGE消息,如建议 这里. 。昨天,我遇到了一个问题,因为事实证明我必须使用WMI来检索一些模糊的磁盘细节,例如序列号。事实证明,从wndproc内部调用WMI例程会抛出脱节的context 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的使用是否意味着系统。管理功能的多线程和特殊处理?

同时,我发现了另一个与该MDA有关的问题,这是 这里. 。好的,我可以认为调用wmi意味着为基础com组件创建一个单独的线程 - 但是我仍然不会发生为什么在按下按钮后调用时不需要魔术,而在调用时需要做魔术它来自wndproc。

我对此感到非常困惑,并感谢您对此进行一些澄清。只有少数比有解决方案更糟糕的事情,而不知道为什么它起作用:/

欢呼,亚历山大

有帮助吗?

解决方案

关于COM公寓和消息泵的讨论很长 这里. 。但是,主要的观点是使用消息泵来确保对STA中的呼叫进行正确的申请。由于UI线程是有关的sta,因此需要泵送消息,以确保一切正常工作。

WM_DeviceChange消息实际上可以多次发送到窗口。因此,在直接致电GetDrives的情况下,您有效地获得了递归电话。在GetDrives呼叫上放一个断点,然后连接设备以发射事件。

您第一次碰到突破点,一切都很好。现在按F5继续,您将第二次点击突破点。这次呼叫堆栈就像:

睡觉,等待或加入] DeletemewIndowsForms.exe!deletemewindowsforms.form.form1.wndproc(ref System.windows.windows.forms.message m)行46 c#system.windows.forms.dll!system.dll! .onmessage(Ref System.Windows.Forms.Message M)
system.windows.forms.dll!system.windows.forms.control.controlnativewindow.wndproc(ref system.windows.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 bytes + 0x64 bytes [本机为托管过渡
设法到本地过渡
mscorlib.dll!system.threading.waithandle.internalwaitone(system.runtime.interopservices.safehandle oawablesafehandle,long millisecondStimeout,bool hasthreadaffility,bool exitContext) + 0x2b bytes mscorlib.dll! ) + 0x2d字节
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.CreateWBESTPATH(String path) + 0x18字节system.management.dll!system.Management.Management.ManagementClass.ManagementClass.ManagementClass(String path) + 0x29字节BYTES
deletemewindowsforms.exe!deletemewindowsforms.form1.getDrives()行23 + 0x1b字节C#

因此,有效地正在泵送窗口消息,以确保com调用正确编辑,但这具有致电WNDProc的副作用,并再次在先前的GetDrives呼叫中再次拨打WNDPROC并再次拨打getDrives(因为有待处理的WM_DeviceChange消息)。当您使用BeginInvoke时,您将删除此递归电话。

同样,在第一次打击后,在GetDrives的电话上打个断点,然后按F5。下一次,等待一两秒钟,然后再次按F5。有时它会失败,有时不会失败,您会再次碰到断点。这次,您的呼叫列表将包含三个通话来进行GetDrives,最后一个是由DiskDrivelist Collection列举触发的。因为再次将消息泵送,以确保通话已被填写。

很难确切地指出为什么 MDA 被触发,但是鉴于递归调用可以合理地假设COM上下文可以过早地撕下和/或在可以发布基础com对象之前收集对象。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top