This was triggered by another question I was looking at. It might be too long to read, so please bear with me.
Apparently, CoWaitForMultipleHandles
does not behave as documented on MSDN.
The code below (based upon the original question) is a console app, which starts an STA thread with a test Win32 window and tries post and to pump some messages. It does three different tests on CoWaitForMultipleHandles
, all without COWAIT_WAITALL
flag.
Test #1 is aimed to verify this:
COWAIT_INPUTAVAILABLE If set, the call to CoWaitForMultipleHandles will return S_OK if input exists for the queue, even if the input has been seen (but not removed) using a call to another function, such as PeekMessage.
This is not happening, CoWaitForMultipleHandles
blocks and doesn't return until the wait handle is signalled. I do assume that any pending message should be treated as input (same as with MWMO_INPUTAVAILABLE
of MsgWaitForMultipleObjectsEx
, which works as expected).
Test #2 is aimed to verify this:
COWAIT_DISPATCH_WINDOW_MESSAGES Enables dispatch of window messages from CoWaitForMultipleHandles in an ASTA or STA. Default in ASTA is no window messages dispatched, default in STA is only a small set of special-cased messages dispatched. The value has no meaning in MTA and is ignored.
This doesn't work either. When CoWaitForMultipleHandles
is called with COWAIT_DISPATCH_WINDOW_MESSAGES
flag alone, it instantly returns error CO_E_NOT_SUPPORTED
(0x80004021). If it's a combination of COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS
, the call blocks but doesn't pump any messages.
Test #3 demonstrates the only way I could make CoWaitForMultipleHandles
pump the Windows message queue of the calling thread. It is a combination of COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE
. This does pump and dispatch messages, although apparently it is an undocumented behaviour.
The test code (a ready-to-run console app):
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;
}
}
The output:
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.
All tests are done under Windows 8.1 Pro 64bit + NET v4.5.1.
Am I misreading the docs or missing something else?
Should I report this as a bug (at least, a bug in the docs)?
Should
CoWaitForMultipleHandles
be avoided and replaced with a solution based onMsgWaitForMultipleObjectsEx
(which behaves in accordance with the docs)?
[UPDATE] Under Windows 7, neither COWAIT_DISPATCH_WINDOW_MESSAGES
nor COWAIT_DISPATCH_CALLS
are supported, CoWaitForMultipleHandles
fails with E_INVALIDARG
(0x80070057). When called with zero as flags, it blocks without pumping.