CoWaitForMultipleHandles API的行为与记录不符

nos*_*tio 10 .net c# windows com winapi

这是由我正在研究的另一个问题引发的.阅读可能太长了,所以请耐心等待.

显然,在MSDN CoWaitForMultipleHandles没有记录的行为.

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

测试#1旨在验证这一点:

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

这不会发生,CoWaitForMultipleHandles阻塞并且在发出等待句柄之前不会返回.我不认为任何未决的消息应被视为输入(与同MWMO_INPUTAVAILABLEMsgWaitForMultipleObjectsEx,它按预期工作).

测试#2旨在验证这一点:

COWAIT_DISPATCH_WINDOW_MESSAGES允许从ASTA或STA中的CoWaitForMultipleHandles分派窗口消息.ASTA中的默认值是没有调度的窗口消息,STA中的默认值只是一小部分特殊的消息调度.该值在MTA中没有意义,将被忽略.

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

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

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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下完成.

  • 我误读了文档还是遗漏了其他内容?

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

  • 应该CoWaitForMultipleHandles避免并替换为基于MsgWaitForMultipleObjectsEx(根据文档行为)的解决方案?

[更新]在Windows 7下,既不支持COWAIT_DISPATCH_WINDOW_MESSAGES也不COWAIT_DISPATCH_CALLS支持,CoWaitForMultipleHandles失败E_INVALIDARG (0x80070057).当以零作为标志调用时,它会在没有泵送的情况下阻塞

ace*_*ent 5

CoWaitForMultipleHandles旨在在 STA 中处理 COM 窗口消息(例如跨单元编组)和其他一些消息(不要问我是哪一个),或者只是在 MTA 中进行阻塞。在Chris Brumme 的这篇博客文章 \xc2\xabManagedblocking\xc2\xbb中,它表示CWFMH处理“适量”的窗口消息。但是,由于它将任何非 COM 发布的窗口消息留在队列中,因此队列可能仍会填满,只是没有 COM 窗口消息。

\n\n

根据此文档 \xc2\xabMigration your Windows 8 Consumer Preview app to Windows 8 Release Preview\xc2\xbb,它说:

\n\n
\n

Windows 应用商店应用程序不再支持 CoWaitForMultipleHandles 函数。此外,以下 CoWait_Flags 已被删除:

\n\n

COWAIT_DISPATCH_CALLS

\n\n

COWAIT_DISPATCH_WINDOW_MESSAGES

\n
\n\n

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

\n\n

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

\n\n

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

\n\n

请注意,这仅适用于一个手柄;对于多个句柄(例如互斥锁),您需要全部或全部都不使用,下一个最佳方法是主动循环,其中超时调用后跟窗口WaitForMultipleObjects消息循环。这是一种边界情况,对于用户关注中心的 UI 应用程序来说是可以接受的(例如,这是他们的主要工作),但如果此类代码可以无人值守且按需运行,则这是不可接受的。除非您确定它需要在 STA 或 UI 线程中发生,否则我的建议是不要这样做。PeekMessagePM_REMOVE

\n\n

最后,您可能应该在 Microsoft Connect 上打开一个错误,至少更新文档。或者实际上让它按“预期”工作。

\n

  • +1,一个很好的答案,我希望它是在一周前发布的:)我已经做了几乎相同的研究:http://stackoverflow.com/a/21371891/1768303。 (2认同)