调用 ShellExecute 似乎会从 Message Queue 中删除消息。下面提供了用于测试此行为的 C 程序的源代码。请注意,我使用 ShellExecute 在用户的默认浏览器中打开 URL(似乎是执行此操作的推荐 API)。
我有问题:
一方面,这似乎不太可能是一个错误,因为它可能会影响足够多的程序来修复(如果它发生在足够多的 Windows 版本上)。另一方面,删除消息只会在某些时候引起问题(即,如果队列为空或当时只有不重要的消息,则不会出现问题)。我也找不到任何提及此行为的文档,也找不到在具有消息队列的线程上调用 ShellExecute 存在任何问题的文档。此外,ShellExecute 可能不会经常被调用,这使得此清单更加间歇性。
如果您能够在另一个版本的 Windows(我的版本是 10.0.19045)上自行编译/运行该程序并报告结果,这将很有帮助。如果它重现了问题,您将在末尾看到“错误:”消息。
我还尝试通过“开发者平台”>“API 反馈”类别中的“反馈中心”向 Microsoft 提交错误,但如果有人知道提交此潜在错误的更合适的位置,请告诉我。
// Compile from Visual Studio Command Prompt with:
//
// cl main.c
//
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "shell32.lib")
#define UNICODE
#include <windows.h>
#include <stdio.h>
static unsigned DropMessages()
{
unsigned count = 0;
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
printf(" droping message %d\n", msg.message);
count++;
}
if (count == 0) {
printf(" empty (no messages were dropped)\n");
}
return count;
}
// NOTE: using wWinMain doesn't help
//int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, WCHAR *pCmdLine, int nCmdShow)
int main()
{
//
// Show that the message queue starts out empty
//
printf("dropping messages, it should start out empty:\n");
{
unsigned dropped = DropMessages();
if (dropped != 0) abort();
}
//
// Post a message to the queue and show that it's in there
//
printf("posting message\n");
if (!PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0)) {
printf("PostThreadMessage failed, error={}\n", GetLastError());
return -1;
}
printf("dropping messages, it should drop 1:\n");
{
unsigned dropped = DropMessages();
if (dropped != 1) abort();
}
//
// Post a message to the queue, call ShellExecute which clears the queue!?!
//
printf("posting message\n");
if (!PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0)) {
printf("PostThreadMessage failed, error={}\n", GetLastError());
return -1;
}
static const int enable_bug = 1;
if (enable_bug) {
printf("calling ShellExecute, this clears the message queue!?!\n");
INT_PTR result = (INT_PTR)ShellExecute(NULL, NULL, NULL, NULL, NULL, 0);
if (result <= 32) {
printf("ShellExecute failed, result={}, error={}\n", result, GetLastError());
return -1;
}
}
printf("dropping messages, you'd think it should have 1 but:\n");
{
unsigned dropped = DropMessages();
if (dropped == 1) {
printf("Success!\n");
} else {
printf("Error: expected to drop exactly 1 message but dropped %d\n", dropped);
}
}
return 0;
}
正如 IInspectable 所说,
ShellExecute
将始终在调用线程上实例化 COM 对象。由于您无法再控制线程而丢失消息。
文档对此进行了解释:由于 ShellExecute 可以将执行委托给使用组件对象模型 (COM) 激活的 Shell 扩展(数据源、上下文菜单处理程序、动词实现),因此应在调用 ShellExecute 之前初始化 COM。某些 Shell 扩展需要 COM 单线程单元 (STA) 类型。
而且它并不特定于
ShellExecute
。启动模式对话框将表现出相同的行为。
所以丢失消息是设计使然。