CoWaitForMultipleHandles API的行为与记录不符

时间:2014-01-20 05:04:06

标签: c# .net windows winapi com

这是由another question我所看到的。阅读时间可能太长,所以请耐心等待。

显然,CoWaitForMultipleHandles 的行为与MSDN上记录的相同。

下面的代码(基于原始问题)是一个控制台应用程序,它启动一个带有测试Win32窗口的STA线程并尝试发布并抽取一些消息。它对CoWaitForMultipleHandles进行了三次不同的测试,所有没有 COWAIT_WAITALL标记。

测试#1 旨在验证this

  

COWAIT_INPUTAVAILABLE 如果设置,则调用CoWaitForMultipleHandles   如果队列存在输入,则返回S_OK,即使输入有   已经看到(但没有删除)使用另一个函数的调用,如   的PeekMessage。

这没有发生,CoWaitForMultipleHandles阻塞,直到等待句柄发出信号才返回。我确实假设任何待处理的消息都应被视为输入(与MWMO_INPUTAVAILABLE的{​​{1}}相同,其效果与预期一致。)

测试#2 旨在验证this

  

COWAIT_DISPATCH_WINDOW_MESSAGES 启用窗口消息的分派   来自ASTA或STA中的CoWaitForMultipleHandles。 ASTA中的默认值为no   调度窗口消息,默认在STA中只是一小部分   发送特殊信息。该值在MTA中没有意义   被忽略了。

这也不起作用。仅使用MsgWaitForMultipleObjectsEx标志调用CoWaitForMultipleHandles时,它会立即返回错误COWAIT_DISPATCH_WINDOW_MESSAGES(0x80004021)。如果它是CO_E_NOT_SUPPORTED的组合,则呼叫阻止,但不会提取任何消息。

测试#3 演示了我可以使COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS泵送调用线程的Windows消息队列的唯一方法。它是CoWaitForMultipleHandles的组合。 这会发送和发送消息,但显然这是一种无证件行为。

测试代码(可立即运行的控制台应用):

COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE

输出:

Starting an STA thread...
Post some WM_TEST messages...

Test #1. CoWaitForMultipleHandles with COWAIT_INPUTAVAILABLE only, press Enter to stop...

Result: 0, pending messages in the queue: True

Test #2. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, press Enter to stop...

Result: 0, pending messages in the queue: True

Test #3. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE, press Enter to stop...
WM_TEST processed: 1
WM_TEST processed: 2
WM_TEST processed: 3

Result: 0, pending messages in the queue: False

STA thread finished.
Press Enter to exit.

所有测试均在Windows 8.1 Pro 64bit + NET v4.5.1下完成。

  • 我误读了文档或遗漏了其他内容吗?

  • 我应该将此报告为错误(至少是文档中的错误)吗?

  • 是否应该避免使用using System; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace ConsoleTestApp { static class Program { // Main static void Main(string[] args) { Console.WriteLine("Starting an STA thread..."); RunStaThread(); Console.WriteLine("\nSTA thread finished."); Console.WriteLine("Press Enter to exit."); Console.ReadLine(); } // start and run an STA thread static void RunStaThread() { var thread = new Thread(() => { // create a simple Win32 window IntPtr hwnd = CreateTestWindow(); // Post some WM_TEST messages Console.WriteLine("Post some WM_TEST messages..."); NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(1), IntPtr.Zero); NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(2), IntPtr.Zero); NativeMethods.PostMessage(hwnd, NativeMethods.WM_TEST, new IntPtr(3), IntPtr.Zero); // Test #1 Console.WriteLine("\nTest #1. CoWaitForMultipleHandles with COWAIT_INPUTAVAILABLE only, press Enter to stop..."); var task = ReadLineAsync(); uint index; var result = NativeMethods.CoWaitForMultipleHandles( NativeMethods.COWAIT_INPUTAVAILABLE, NativeMethods.INFINITE, 1, new[] { task.AsUnmanagedHandle() }, out index); Console.WriteLine("Result: " + result + ", pending messages in the queue: " + (NativeMethods.GetQueueStatus(0x1FF) >> 16 != 0)); // Test #2 Console.WriteLine("\nTest #2. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, press Enter to stop..."); task = ReadLineAsync(); result = NativeMethods.CoWaitForMultipleHandles( NativeMethods.COWAIT_DISPATCH_WINDOW_MESSAGES | NativeMethods.COWAIT_DISPATCH_CALLS, NativeMethods.INFINITE, 1, new[] { task.AsUnmanagedHandle() }, out index); Console.WriteLine("Result: " + result + ", pending messages in the queue: " + (NativeMethods.GetQueueStatus(0x1FF) >> 16 != 0)); // Test #3 Console.WriteLine("\nTest #3. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE, press Enter to stop..."); task = ReadLineAsync(); result = NativeMethods.CoWaitForMultipleHandles( NativeMethods.COWAIT_DISPATCH_WINDOW_MESSAGES | NativeMethods.COWAIT_DISPATCH_CALLS | NativeMethods.COWAIT_INPUTAVAILABLE, NativeMethods.INFINITE, 1, new[] { task.AsUnmanagedHandle() }, out index); Console.WriteLine("Result: " + result + ", pending messages in the queue: " + (NativeMethods.GetQueueStatus(0x1FF) >> 16 != 0)); }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); } // // Helpers // // create a window to handle messages static IntPtr CreateTestWindow() { // Create a simple Win32 window var hwndStatic = NativeMethods.CreateWindowEx(0, "Static", String.Empty, NativeMethods.WS_POPUP, 0, 0, 0, 0, NativeMethods.HWND_MESSAGE, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); // subclass it with a custom WndProc IntPtr prevWndProc = IntPtr.Zero; NativeMethods.WndProc newWndProc = (hwnd, msg, wParam, lParam) => { if (msg == NativeMethods.WM_TEST) Console.WriteLine("WM_TEST processed: " + wParam); return NativeMethods.CallWindowProc(prevWndProc, hwnd, msg, wParam, lParam); }; prevWndProc = NativeMethods.SetWindowLong(hwndStatic, NativeMethods.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc)); if (prevWndProc == IntPtr.Zero) throw new ApplicationException(); return hwndStatic; } // call Console.ReadLine on a pool thread static Task<string> ReadLineAsync() { return Task.Run(() => Console.ReadLine()); } // get Win32 waitable handle of Task object static IntPtr AsUnmanagedHandle(this Task task) { return ((IAsyncResult)task).AsyncWaitHandle.SafeWaitHandle.DangerousGetHandle(); } } // Interop static class NativeMethods { [DllImport("user32")] public static extern IntPtr SetWindowLong(IntPtr hwnd, int nIndex, IntPtr dwNewLong); [DllImport("user32")] public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] public static extern IntPtr CreateWindowEx( uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); [DllImport("user32.dll")] public static extern bool PostMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] public static extern int MessageBox(IntPtr hwnd, string text, String caption, int options); [DllImport("ole32.dll", SetLastError = true)] public static extern uint CoWaitForMultipleHandles(uint dwFlags, uint dwTimeout, int cHandles, IntPtr[] pHandles, out uint lpdwindex); [DllImport("user32.dll")] public static extern uint GetQueueStatus(uint flags); [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate IntPtr WndProc(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam); public static IntPtr HWND_MESSAGE = new IntPtr(-3); public const int GWL_WNDPROC = -4; public const uint WS_POPUP = 0x80000000; public const uint WM_USER = 0x0400; public const uint WM_TEST = WM_USER + 1; public const uint COWAIT_WAITALL = 1; public const uint COWAIT_ALERTABLE = 2; public const uint COWAIT_INPUTAVAILABLE = 4; public const uint COWAIT_DISPATCH_CALLS = 8; public const uint COWAIT_DISPATCH_WINDOW_MESSAGES = 0x10; public const uint RPC_S_CALLPENDING = 0x80010115; public const uint WAIT_TIMEOUT = 0x00000102; public const uint WAIT_FAILED = 0xFFFFFFFF; public const uint WAIT_OBJECT_0 = 0; public const uint WAIT_ABANDONED_0 = 0x00000080; public const uint WAIT_IO_COMPLETION = 0x000000C0; public const uint INFINITE = 0xFFFFFFFF; } } 替换为基于MsgWaitForMultipleObjectsEx的解决方案(根据文档行事)?

[更新] 在Windows 7下,CoWaitForMultipleHandlesCOWAIT_DISPATCH_WINDOW_MESSAGES均未受支持,COWAIT_DISPATCH_CALLSCoWaitForMultipleHandles(0x80070057)而失败。当以零作为标志调用时,它会在没有泵送的情况下阻塞

1 个答案:

答案 0 :(得分:3)

CoWaitForMultipleHandles旨在处理STA中的COM窗口消息(例如跨公寓编组)和其他一些(不要问我哪个),或者只是在MTA中阻止。在this blog post, «Managed blocking» by Chris Brumme中,它表示CWFMH处理“恰当数量”的窗口消息。但是,由于它在队列中留下了任何非COM发布的窗口消息,因此队列可能仍然填满,而不是COM窗口消息。

根据this document, «Migrating your Windows 8 Consumer Preview app to Windows 8 Release Preview»,它说:

  

Windows应用商店应用不再支持CoWaitForMultipleHandles功能。此外,还删除了以下CoWait_Flags:

     

COWAIT_DISPATCH_CALLS

     

COWAIT_DISPATCH_WINDOW_MESSAGES

如果您确实要处理所有邮件,则应在MsgWaitForMultipleObjectsExGetMessagePeekMessage的邮件循环中使用PM_REMOVE。这样做意味着潜在的重入狂潮。您仍然无法控制从堆栈中的其他组件进一步调用STA。也就是说,模式对话框(例如,用于打开的公共对话框)可能会在一个普通的窗口消息循环中抽取每条消息,但某些框架可能会调用CoWaitForMultipleHandles

底线是,如果您正在进行密集处理或阻塞操作,请将其委托给另一个线程(可能使用队列),如果需要,请在操作完成后告知调用UI线程更新。

这与例如不同冗长的UI调用,例如OLE嵌入或模式对话,其中通常在堆栈的某处存在窗口消息循环。或者通过冗长但可重复/可恢复的操作(例如状态机),您可以通过偶尔处理消息来协作,或者使用在有消息时返回的等待函数,以便您可以在再次等待之前处理它们。

小心,这只适用于一个手柄;多个手柄,例如互斥体,您需要全部或全部,并且下一个最佳方法是一个活动循环,对WaitForMultipleObjects的超时调用后跟PeekMessage PM_REMOVE窗口消息循环。这是一个边界线的情况,对于作为用户注意力中心的UI应用程序是可接受的(例如,这是他们的主要工作),但如果这样的代码可以无人值守和按需运行,则是不可接受的。除非你确定它需要在STA或UI线程中发生,否则我的建议是不要这样做。

最后,您应该在Microsoft Connect上打开一个错误,至少要更新文档。或者实际上让它像预期的那样工作。