gpt4 book ai didi

c# - CoWaitForMultipleHandles API 的行为与记录不符

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

这是由 another question 触发的我在看。阅读时间可能太长,请耐心等待。

显然,CoWaitForMultipleHandles 的行为与 MSDN 上记录的一样。

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

测试 #1 旨在验证 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.

这不会发生,CoWaitForMultipleHandles 会阻塞并且不会返回,直到等待句柄发出信号。我确实假设任何未决消息都应被视为输入(与 MsgWaitForMultipleObjectsExMWMO_INPUTAVAILABLE 相同,它按预期工作)。

测试 #2 旨在验证 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.

这也行不通。当单独使用 COWAIT_DISPATCH_WINDOW_MESSAGES 标志调用 CoWaitForMultipleHandles 时,它会立即返回错误 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;
}
}

输出:

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: TrueTest #2. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, press Enter to stop...Result: 0, pending messages in the queue: TrueTest #3. CoWaitForMultipleHandles with COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE, press Enter to stop...WM_TEST processed: 1WM_TEST processed: 2WM_TEST processed: 3Result: 0, pending messages in the queue: FalseSTA thread finished.Press Enter to exit.

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

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

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

  • 是否应该避免 CoWaitForMultipleHandles 并替换为基于 MsgWaitForMultipleObjectsEx 的解决方案(其行为与文档一致)?

[UPDATE] 在 Windows 7 下,COWAIT_DISPATCH_WINDOW_MESSAGESCOWAIT_DISPATCH_CALLS 均不受支持,CoWaitForMultipleHandles 失败并返回 E_INVALIDARG (0x80070057)。当以零作为标志调用时,它会在不抽取的情况下阻塞。

最佳答案

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» ,它说:

CoWaitForMultipleHandles function is no longer supported in Windows Store apps. In addition the following CoWait_Flags have been removed:

COWAIT_DISPATCH_CALLS

COWAIT_DISPATCH_WINDOW_MESSAGES

如果你真的想处理所有消息,你应该在消息循环中使用 MsgWaitForMultipleObjectsExGetMessage 或者 PeekMessagePM_REMOVE 。这样做意味着潜在的重入狂潮。您仍然无法控制堆栈中其他组件对 STA 的进一步调用。也就是说,模态对话框(例如打开的通用对话框)可能会在普通的旧窗口消息循环中抽取每条消息,但某些框架可能会调用 CoWaitForMultipleHandles

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

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

小心,这只适用于一个 handle ;对于多个 handle ,例如互斥体,你想要全部或没有,下一个最好的方法是一个主动循环,超时调用 WaitForMultipleObjects 然后是 PeekMessagePM_REMOVE 窗口消息循环。这是一个边界线案例,对于作为用户关注中心的 UI 应用程序来说是可以接受的(例如,这是他们的主要工作),但如果此类代码可能在无人值守和按需运行时是 Not Acceptable 。除非您确定它需要在 STA 或 UI 线程中发生,否则我的建议是不要这样做。

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

关于c# - CoWaitForMultipleHandles API 的行为与记录不符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21226600/

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