在MFC中使用Qt(六):深入了解QMfcApp

发布于:2025-04-01 ⋅ 阅读:(21) ⋅ 点赞:(0)

前言

此前系列文章回顾:
在MFC中使用Qt(一):玩腻了MFC,试试在MFC中使用Qt!(手动配置编译Qt)

在MFC中使用Qt(二):实现Qt文件的自动编译流程

在MFC中使用Qt(三):通过编辑项目文件(.vcxproj)实现Qt的自动化编译流程

在MFC中使用Qt(四):使用属性表(Property Sheet)实现自动化Qt编译流程

在MFC中使用Qt(五):MFC和Qt的共存和交互

通过源码qt-solutions开源项目可知QMfcApp 通过以下几个关键技术点实现 MFC 和 Qt 事件循环的共存:
1.使用Windows钩子来监控消息队列,确保Qt事件在MFC的消息循环中被处理。
2.重写事件过滤器(winEventFilter)来处理Windows消息。
3.在Qt的事件循环中集成MFC的消息泵,通过定期处理MFC的Onldl或消息分发。
4.管理模态循环的计数,调整Qt的事件处理行为,避免在模态期间出现问题。
5.静态方法run0和instance0帮助初始化和协调两个框架的事件循环。

本文将详细说明其工作原理。

深入了解QMfcApp

Windows钩子(Hook)确保事件同步

关键代码:

// 安装 WH_GETMESSAGE 钩子监控消息队列
hhook = SetWindowsHookEx(WH_GETMESSAGE, QtFilterProc, 0, GetCurrentThreadId());

LRESULT CALLBACK QtFilterProc(...) {
    // 强制刷新 Qt 事件队列,防止事件积压
    qApp->sendPostedEvents();
    return CallNextHookEx(...);
}

作用:
在每次 Windows 消息被取出(GetMessage 或 PeekMessage)时,触发钩子回调,确保 Qt 的 DeferredDelete 等事件及时处理。

​避免阻塞:即使 MFC 窗口模态对话框阻塞了 Qt 主循环,钩子仍能保证 Qt 事件被处理。

重写事件过滤器(winEventFilter)

关键代码:

bool QMfcApp::winEventFilter(MSG *msg, long *result)
{
    static bool recursion = false;
    if (recursion)
        return false;

    recursion = true;

    QWidget *widget = QWidget::find((WId)msg->hwnd);
    HWND toplevel = 0;
    if (widget) {
        HWND parent = (HWND)widget->winId();
        while(parent) {
            toplevel = parent;
            parent = GetParent(parent);
        }
        HMENU menu = toplevel ? GetMenu(toplevel) : 0;
        if (menu && GetFocus() == msg->hwnd) {
            if (msg->message == WM_SYSKEYUP && msg->wParam == VK_MENU) {
                // activate menubar on Alt-up and move focus away
                SetFocus(toplevel);
                SendMessage(toplevel, msg->message, msg->wParam, msg->lParam);
                widget->setFocus();
                recursion = false;
                return TRUE;
            } else if (msg->message == WM_SYSKEYDOWN && msg->wParam != VK_MENU) {
                SendMessage(toplevel, msg->message, msg->wParam, msg->lParam);
                SendMessage(toplevel, WM_SYSKEYUP, VK_MENU, msg->lParam);
                recursion = false;
                return TRUE;
            }
        }
    }
#ifdef QTWINMIGRATE_WITHMFC
    else if (mfc_app) {
        MSG tmp;
        while (doIdle && !PeekMessage(&tmp, 0, 0, 0, PM_NOREMOVE)) {
            if (!mfc_app->OnIdle(idleCount++))
                doIdle = FALSE;
        }
        if (mfc_app->IsIdleMessage(msg)) {
            doIdle = TRUE;
            idleCount = 0;
        }
    }
    if (mfc_app && mfc_app->PreTranslateMessage(msg)) {
        recursion = false;
    return TRUE;
    }
#endif

    recursion = false;
#if QT_VERSION < 0x050000
    return QApplication::winEventFilter(msg, result);
#else
    Q_UNUSED(result);
    return false;
#endif
}
  • 作用QMfcApp::winEventFilter 是 Qt 与 MFC 消息协调的核心枢纽,其核心目标是 ​优先让 MFC 处理关键消息(如菜单、快捷键、空闲任务),剩余消息交由 Qt 处理
  • 处理逻辑
    • winEventFilter 中优先处理 MFC 消息(如 WM_ENTERIDLE),调用 MFC 的 OnIdle 等接口。
    • 剩余消息交给 Qt 的标准处理流程 QApplication::winEventFilter()

Qt 主循环驱动 MFC 消息泵

关键代码:

// 伪代码:Qt 主循环内部逻辑
int QApplication::exec() {
    while (!exit_loop) {
        // 处理 Qt 事件
        processEvents(); 

        // 嵌入 MFC 消息泵逻辑
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg); // MFC 窗口处理消息
        }
    }
    return exit_code;
}

关键点
Qt 的 exec() 并非单纯阻塞,其内部会通过 ​非阻塞轮询(PeekMessage)​ 持续检查并分发 Windows 消息。

  • PeekMessage 代替了 MFC 的 PumpMessage,允许在 Qt 主循环中处理 MFC 窗口消息。
  • MFC 无需独立事件循环:所有消息最终由 DispatchMessage 分发到 MFC 窗口过程(如 CWnd::WindowProc)。

模态循环协同

关键代码:

// 进入/退出模态循环时更新计数器
void QMfcApp::enterModalLoop() { ++modalLoopCount; }
void QMfcApp::exitModalLoop()  { --modalLoopCount; }

作用:在 MFC 模态对话框(如 MessageBox)期间调整 Qt 事件处理策略。

逻辑细节

  • 进入模态时暂停 Qt 的 DeferredDelete 事件,防止对象在模态期间被销毁。
  • 退出时恢复事件处理,确保 UI 状态一致。

绕过MFC 的 CWinApp::Run

QMfcApp 的设计中,​MFC 的消息循环并未独立启动,而是通过 ​将 MFC 消息泵嵌入到 Qt 的主事件循环 中实现共存。

// 典型 MFC 应用的主循环(被 QMfcApp 替换)
int CWinApp::Run() {
    while (PumpMessage()) {} // 传统 MFC 消息泵
    return ExitInstance();
}

// QMfcApp 的替代实现
int QMfcApp::run(CWinApp *mfcApp) {
    qApp->exec();          // 启动 Qt 主循环(内含 MFC 消息泵)
    mfcApp->ExitInstance(); // 清理 MFC 资源
}

关键设计

  • 不调用 CWinApp::Run:QMfcApp 完全接管消息循环,避免 MFC 自身循环启动。
  • 资源清理:Qt 主循环退出后,手动调用 ExitInstance 完成 MFC 清理。

总结

QMfcApp ​未同时运行两个独立事件循环,而是通过以下方式实现协作而非并行:

  1. Qt 主循环驱动:通过 PeekMessage 处理所有消息(包括 MFC 窗口消息)。
  2. MFC 逻辑嵌入:消息过滤器和钩子确保 MFC 的 PreTranslateMessageOnIdle 等关键逻辑被触发。
  3. 无阻塞设计:Qt 的 exec() 内部以非阻塞方式轮询消息,避免独占线程。

这种设计使得 MFC 窗口能够响应消息,而 Qt 控件也能正常更新,实现无缝混合运行。