消息泵闲置时 User32 SendMessage 挂起
User32 SendMessage hanging when message pump is sitting idle
我有一个用于第三方应用程序的多线程 dll。我的 dll 通过使用自定义消息类型调用 SendMessage 将消息调用到主 UI 线程:
typedef void (*CallbackFunctionType)();
DWORD _wm;
HANDLE _hwnd;
DWORD threadId;
Initialize()
{
_wm = RegisterWindowMessage("MyInvokeMessage");
WNDCLASS wndclass = {0};
wndclass.hInstance = (HINSTANCE)&__ImageBase;
wndclass.lpfnWndProc = wndProcedure;
wndclass.lpszClassName = "MessageOnlyWindow";
RegisterClass(&wndclass);
_hwnd = CreateWindow(
"MessageOnlyWindow",
NULL,
NULL,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
(HINSTANCE)&__ImageBase,
NULL);
threadId = GetCurrentThreadId();
}
void InvokeSync(CallbackFunctionType funcPtr)
{
if (_hwnd != NULL && threadId != GetCurrentThreadId())
SendMessage(_hwnd, _wm, 0, (LPARAM)funcPtr);
else
funcPtr();
}
static LRESULT CALLBACK wndProcedure(
HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == _wm)
{
CallbackFunctionType funcPtr = (CallbackFunctionType)lParam;
(*funcPtr)();
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
应用程序是MDI,我正在background/save中对一堆文档执行open document/extract contents/process,所以它不断地切换活动文档并打开和关闭新的一个。
我的问题是,有时在尝试使用上述 InvokeSync() 函数将消息调用到主线程时,处理会卡住。
当我在调试器中暂停它时,我看到主线程有这个调用堆栈:
user32.dll!_NtUserGetMessage@16() + 0x15 bytes
user32.dll!_NtUserGetMessage@16() + 0x15 bytes
mfc42.dll!CWinThread::PumpMessage() + 0x16 bytes
// the rest is normal application stuff
而被锁定的后台线程有一个这样的调用堆栈:
user32.dll!_NtUserMessageCall@28() + 0x15 bytes
user32.dll!_NtUserMessageCall@28() + 0x15 bytes
mydll!InvokeSync(funcPtr)
// the rest is expected dll stuff
所以它似乎卡在 "SendMessage()" 调用上,但据我所知,主线程上的消息泵闲置在那里。
但是,如果我手动单击一个非活动文档(使其处于活动状态),这会以某种方式唤醒所有内容,并且 SendMessage() 事件最终通过,并恢复处理。
主应用程序使用 Microsoft Fibers,每个文档 1 个纤程。我的 SendMessage 是否会卡在已关闭的后台光纤中?在光纤进入非活动状态之前就在光纤上,并且只有通过强制上下文切换,光纤才能处理它的消息?真不明白线和纤维是怎么相互作用的,所以有点摸不着头脑。
什么会导致邮件像这样未经处理地坐在那里?更重要的是,有没有办法避免这种情况的发生呢?或者至少,我该如何调试这种情况?
我继续实现了自己的消息队列,以及一种消息格式,它使用信号量在收到消息时进行通知,在完成时使用另一个信号量,然后每 1 秒重复一次 PostMessage 直到 "message received" 收到信号,然后等待无限超时的 "message complete"。
任何额外的 PostMessages 都将被忽略,因为它们不再包含要执行的负载,它们只是告诉主线程检查传入事件的队列。
自从我进行了这些更改后,我还没有 运行 再次遇到这种情况。据我所知,发送的消息必须在已关闭光纤的队列中结束,并在该光纤再次接通之前被遗忘。通过重新发布消息,它可以不断重试,直到有源光纤注意到消息就在那里。
检查 GetMessage
的参数。第三个和第四个是消息ID范围。如果您的消息 ID 超出此范围,您的消息将很乐意进入队列。
我有一个用于第三方应用程序的多线程 dll。我的 dll 通过使用自定义消息类型调用 SendMessage 将消息调用到主 UI 线程:
typedef void (*CallbackFunctionType)();
DWORD _wm;
HANDLE _hwnd;
DWORD threadId;
Initialize()
{
_wm = RegisterWindowMessage("MyInvokeMessage");
WNDCLASS wndclass = {0};
wndclass.hInstance = (HINSTANCE)&__ImageBase;
wndclass.lpfnWndProc = wndProcedure;
wndclass.lpszClassName = "MessageOnlyWindow";
RegisterClass(&wndclass);
_hwnd = CreateWindow(
"MessageOnlyWindow",
NULL,
NULL,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
(HINSTANCE)&__ImageBase,
NULL);
threadId = GetCurrentThreadId();
}
void InvokeSync(CallbackFunctionType funcPtr)
{
if (_hwnd != NULL && threadId != GetCurrentThreadId())
SendMessage(_hwnd, _wm, 0, (LPARAM)funcPtr);
else
funcPtr();
}
static LRESULT CALLBACK wndProcedure(
HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
if (Msg == _wm)
{
CallbackFunctionType funcPtr = (CallbackFunctionType)lParam;
(*funcPtr)();
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
应用程序是MDI,我正在background/save中对一堆文档执行open document/extract contents/process,所以它不断地切换活动文档并打开和关闭新的一个。
我的问题是,有时在尝试使用上述 InvokeSync() 函数将消息调用到主线程时,处理会卡住。
当我在调试器中暂停它时,我看到主线程有这个调用堆栈:
user32.dll!_NtUserGetMessage@16() + 0x15 bytes
user32.dll!_NtUserGetMessage@16() + 0x15 bytes
mfc42.dll!CWinThread::PumpMessage() + 0x16 bytes
// the rest is normal application stuff
而被锁定的后台线程有一个这样的调用堆栈:
user32.dll!_NtUserMessageCall@28() + 0x15 bytes
user32.dll!_NtUserMessageCall@28() + 0x15 bytes
mydll!InvokeSync(funcPtr)
// the rest is expected dll stuff
所以它似乎卡在 "SendMessage()" 调用上,但据我所知,主线程上的消息泵闲置在那里。
但是,如果我手动单击一个非活动文档(使其处于活动状态),这会以某种方式唤醒所有内容,并且 SendMessage() 事件最终通过,并恢复处理。
主应用程序使用 Microsoft Fibers,每个文档 1 个纤程。我的 SendMessage 是否会卡在已关闭的后台光纤中?在光纤进入非活动状态之前就在光纤上,并且只有通过强制上下文切换,光纤才能处理它的消息?真不明白线和纤维是怎么相互作用的,所以有点摸不着头脑。
什么会导致邮件像这样未经处理地坐在那里?更重要的是,有没有办法避免这种情况的发生呢?或者至少,我该如何调试这种情况?
我继续实现了自己的消息队列,以及一种消息格式,它使用信号量在收到消息时进行通知,在完成时使用另一个信号量,然后每 1 秒重复一次 PostMessage 直到 "message received" 收到信号,然后等待无限超时的 "message complete"。
任何额外的 PostMessages 都将被忽略,因为它们不再包含要执行的负载,它们只是告诉主线程检查传入事件的队列。
自从我进行了这些更改后,我还没有 运行 再次遇到这种情况。据我所知,发送的消息必须在已关闭光纤的队列中结束,并在该光纤再次接通之前被遗忘。通过重新发布消息,它可以不断重试,直到有源光纤注意到消息就在那里。
检查 GetMessage
的参数。第三个和第四个是消息ID范围。如果您的消息 ID 超出此范围,您的消息将很乐意进入队列。