gpt4 book ai didi

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

转载 作者:可可西里 更新时间:2023-11-01 09:12:11 25 4
gpt4 key购买 nike

我在 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 调用它有何不同?它们不是发生在我应用程序的同一个主线程上吗?顺便说一句,我的应用程序完全是单线程的,那么为什么突然出现一个错误指的是一些“错误的线程”?使用 WMI 是否意味着多线程和对 System.Management 函数的特殊处理?

与此同时,我发现了另一个与该 MDA 相关的问题,它是 here .好的,我可以认为调用 WMI 意味着为底层 COM 组件创建一个单独的线程 - 但我仍然没有想到为什么在按下按钮后调用它时不需要魔法,而在调用时需要 do-magic它来自 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 调用被正确编码,但这具有再次调用 WndProc 和 GetDrives 的副作用(因为有待处理的 WM_DEVICECHANGE 消息),同时仍在之前的 GetDrives 调用中。当您使用 BeginInvoke 时,您删除了这个递归调用。

再次,在 GetDrives 调用上放置一个断点,并在第一次命中后按 F5。下一次,等待一两秒钟,然后再次按 F5。有时会失败,有时不会,您会再次遇到断点。这一次,您的调用堆栈将包括对 GetDrives 的三个调用,最后一个调用由 diskDriveList 集合的枚举触发。因为同样,消息被抽取以确保调用被编码。

很难准确地指出为什么 MDA被触发,但考虑到递归调用,可以合理地假设 COM 上下文可能会过早拆除和/或在释放基础 COM 对象之前收集对象。

关于c# - 在单线程应用程序中调用 WMI 函数时的 DisconnectedContext MDA,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3921661/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com