0

我有一个用于第三方应用程序的多线程 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,我正在后台执行打开文档/提取内容/进程/保存一堆文档,因此它不断切换活动文档并打开和关闭新文档。

我的问题是,有时处理在尝试使用上述 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 是否会卡在被关闭的背景光纤中?在光纤处于非活动状态或其他情况之前在光纤上,并且只有通过强制上下文切换,该光纤才能处理其消息?我真的不明白线和纤维是如何相互作用的,所以我有点抓住稻草。

什么可能导致消息像这样未经处理而坐在那里?更重要的是,有没有办法防止这种情况发生?或者至少,我该如何调试这种情况?

4

2 回答 2

0

我继续实现了自己的消息队列,以及一种消息格式,它使用信号量来通知何时收到消息,并在消息完成时通知另一个消息格式,然后每 1 秒重复一次 PostMessage 直到“收到消息”发出信号,然后等待无限超时的“消息完成”。

任何额外的 PostMessage 都将被忽略,因为它们不再包含要执行的有效负载,它们只是告诉主线程检查队列中的传入事件。

自从我做了这些改变,我就再也没有遇到过这种情况。我能说的最好的情况是,发送的消息必须在已断开光纤的队列中结束,并且在该光纤再次接通之前被遗忘。通过重新发布消息,它可以继续重试,直到活动光纤注意到该消息。

于 2015-06-23T19:17:17.503 回答
0

检查 的参数GetMessage。第三和第四是消息ID范围。如果其 ID 超出此范围,您的消息将很高兴地坐在队列中。

于 2015-06-23T19:53:00.277 回答