C++实现简化 QtBase(5):通过IEventLoopHost扩展实现win32消息循环

在上一篇文章《C++实现简化版Qt的QObject(4):增加简单实用的事件机制》中,我们实现了普通线程的事件机制。
但是事件机制往往需要和操作系统主线程消息循环一起工作。

因此,今天,我们在之前的CEventLoop的实现之上,通过IEventLoopHost扩展,实现一个windows系统的主线程消息循环扩展,使得我们可以在主线程post task。

CEventLoop 代码

首先,昨天我们写的CEventLoop代码如下:

	class CEventLoop {public:using Clock = std::chrono::steady_clock;using TimePoint = Clock::time_point;using Duration = Clock::duration;using Handler = std::function<void()>;struct TimedHandler {TimePoint time;Handler handler;bool operator<(const TimedHandler& other) const {return time > other.time;}};class IEventLoopHost {public:virtual void onPostTask() = 0;virtual void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) = 0;virtual void onEvent(TimedHandler& event) = 0;virtual void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const TimePoint& timePoint) = 0;};private:IEventLoopHost* host = nullptr;std::priority_queue<TimedHandler> tasks_;std::mutex mutex_;std::condition_variable cond_;std::atomic<bool> running_{ true };public:void setHost(IEventLoopHost* host) {this->host = host;}void post(Handler handler, Duration delay = Duration::zero()) {std::unique_lock<std::mutex> lock(mutex_);tasks_.push({ Clock::now() + delay, std::move(handler) });cond_.notify_one();if (host) {host->onPostTask();}}void run() {while (running_) {std::unique_lock<std::mutex> lock(mutex_);if (tasks_.empty()) {if (host) {host->onWaitForTask(cond_, lock);}else {cond_.wait(lock, [this] { return !tasks_.empty() || !running_; });}}while (!tasks_.empty() && tasks_.top().time <= Clock::now()) {auto task = tasks_.top();tasks_.pop();lock.unlock();if (host) {host->onEvent(task);}else {task.handler();}lock.lock();}if (!tasks_.empty()) {if (host) {host->onWaitForRun(cond_, lock, tasks_.top().time);}else {cond_.wait_until(lock, tasks_.top().time);}}}}void stop() {running_ = false;cond_.notify_all();}};

实现扩展

在Windows API中,消息循环通常涉及到调用GetMessageTranslateMessageDispatchMessage函数。以下是一个IEventLoopHost的实现,它将Windows消息循环集成到CEventLoop类中:

#include <Windows.h>class WindowsEventLoopHost : public CEventLoop::IEventLoopHost {
public:// 当任务被投递到事件循环时调用void onPostTask() override {// 可以使用Windows的PostMessage函数发送一个自定义的消息来唤醒消息循环PostThreadMessage(GetCurrentThreadId(), WM_USER, 0, 0);}// 当事件循环需要等待任务时调用void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) override {locker.unlock();MSG msg;// 使用PeekMessage而不是GetMessage来避免阻塞,以便定时器事件可以被处理while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {TranslateMessage(&msg);DispatchMessage(&msg);}locker.lock();}// 当事件循环处理事件时调用void onEvent(CEventLoop::TimedHandler& event) override {// 这里简单地调用事件处理器event.handler();}// 当事件循环需要等待直到特定时间点运行时调用void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const CEventLoop::TimePoint& timePoint) override {locker.unlock();// 计算需要等待的时间auto now = CEventLoop::Clock::now();auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timePoint - now).count();if (delay_ms > 0) {// 设置Windows计时器SetTimer(NULL, 0, (UINT)delay_ms, NULL);MSG msg;// 等待计时器或其他消息while (GetMessage(&msg, NULL, 0, 0)) {if (msg.message == WM_TIMER) {break; // 计时器消息,继续事件循环}TranslateMessage(&msg);DispatchMessage(&msg);}KillTimer(NULL, 0);}locker.lock();}
};

在这个实现中,我们通过IEventLoopHost的四个虚函数,完成与Windows消息循环协同工作:

  • onPostTask 使用 PostThreadMessage 来发送一个自定义的消息,以唤醒可能正在等待的消息循环。
  • onWaitForTask 使用 PeekMessage 来处理消息而不阻塞,这样定时器消息可以在适当的时间被处理。
  • onEvent 简单地调用传入的事件处理器,这里我们没有对Windows消息做特别的处理。
  • onWaitForRun 使用 SetTimer 来创建一个Windows计时器,它会在指定的毫秒数后发送一个 WM_TIMER 消息。我们使用 GetMessage 等待这个消息或者其他消息的到来。如果计时器消息到来,我们就终止循环,返回到事件循环,以便继续处理其他任务。

继续优化

在自测过程中进一步的优化代码结构,优化性能,处理WM_QUIT消息,处理一些其他细节等。

最终代码如下:

#include <Windows.h>
class CWindowsEventLoopHost : public base::CEventLoop::IEventLoopHost {static constexpr UINT WM_WAKEUP = WM_USER + 15151515;UINT_PTR timerID_ = 0;public:~CWindowsEventLoopHost() {if (timerID_) {KillTimer(NULL, timerID_);}}void onPostTask() override {PostThreadMessage(GetCurrentThreadId(), WM_WAKEUP, 0, 0);}void onWaitForTask(std::condition_variable& cond, std::unique_lock<std::mutex>& locker) override {MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {if (handleWindowsMessage(msg)) {break;}if (cond.wait_for(locker, std::chrono::milliseconds(0), [] { return true; })) {break;}}}void onEvent(base::CEventLoop::TimedHandler& event) override {event.handler();}void onWaitForRun(std::condition_variable& cond, std::unique_lock<std::mutex>& locker, const std::chrono::steady_clock::time_point& timePoint) override {auto now = std::chrono::steady_clock::now();if (now < timePoint) {auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(timePoint - now).count();timerID_ = SetTimer(NULL, 0, (UINT)delay_ms, NULL);//通过timer事件唤醒MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {if (handleWindowsMessage(msg)) {break;}}}}bool handleWindowsMessage(MSG& msg) {if (msg.message == WM_QUIT) {if (this->eventLoop) {this->eventLoop->stop();//退出消息循环}return true;}if (msg.message == WM_TIMER && msg.wParam == timerID_) {KillTimer(NULL, timerID_);timerID_ = 0;return true; // Timer event occurred}TranslateMessage(&msg);DispatchMessage(&msg);return false;}
};

使用示例

由于setHost函数里面没有加锁,而且可能出现在运行过程中被修改host出现不可预期情况。因此,后来去掉setHost函数,改为在CEventLoop的构造函数传入host,避免误用。

使用示例如下:

// 使用示例
int main() {CWindowsEventLoopHost host;base::CEventLoop loop(&host);loop.post([]() {std::cout << "Immediate task\n";});//马上执行loop.post([]() {std::cout << "Delayed task\n";}, std::chrono::seconds(1));//延时一秒loop.post([]() {std::cout << "Delayed task in 3 second\n";}, std::chrono::seconds(3));//延时3秒loop.post([&]() {std::cout << "stop msg loop in 6 second\n";loop.stop();}, std::chrono::seconds(6));//延时6秒if (true) {loop.run();//主线程直接run}else {// 或者起一个其他线程run:std::thread loop_thread([&loop]() {loop.run();});std::this_thread::sleep_for(std::chrono::seconds(10));loop.stop();//停止消息循环loop_thread.join();}
}

完整代码在:https://github.com/kevinyangli/simple_qt_qobject.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/41240.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构-循环链表和双向链表

目录 前言一、循环链表1.1 循环链表的介绍1.2 循环链表的实现 二、双向链表2.1 双向链表的介绍2.2 双向链表的实现 三、循环双链表总结 前言 本篇文章介绍数据结构中的循环链表和双向链表 一、循环链表 1.1 循环链表的介绍 将单链表的形式稍作改变&#xff0c;单链表的最后…

swagger路径怎么设置为不曝露

为了确保 Swagger UI 不暴露在外部访问下&#xff0c;可以采取以下几种方法来限制访问&#xff1a; 方法1&#xff1a;通过认证和授权限制访问 为 Swagger UI 页面设置访问控制&#xff0c;确保只有授权用户才能访问。 1.1 Spring Security 如果使用的是 Spring Boot&#…

centos7一键离线安装NFS服务

在内网环境中安装服务器软件包的离线安装指南 在企业级环境中&#xff0c;出于安全和管理的考虑&#xff0c;许多服务器往往无法连接外网。这种情况下&#xff0c;我们只能通过离线安装包的方式来安装和配置软件。本文将详细介绍如何在内网环境中进行软件包的离线安装。 一、…

Consistency Learning 人话理解

Consistency loss pulls together images and their textual descriptions while pushes away unmatched pairs in the feature space. (from Learning to Prompt for Vision-Language Models) 把相似的样本拉的越近越好&#xff0c;不同的样本越远越好&#xff01; 还可以作为…

c++习题05-斐波那契数列

目录 一&#xff0c;问题 二&#xff0c;思路 三&#xff0c;代码 一&#xff0c;问题 二&#xff0c;思路 根据题目&#xff0c;可以自己列出斐波那契数列&#xff08;前四个&#xff09;如下&#xff1a; 通过列出来的值&#xff0c;可以发现&#xff0c;前两个都是1&…

二维码登录的原理

二维码登录的原理: 二维码登录是一种基于移动设备和网络技术的便捷登录方式。其原理主要依赖于以下几个关键要素: 随机生成:服务器端随机生成一个具有唯一性和时效性的二维码。编码信息:这个二维码包含了特定的登录信息,例如用户标识、会话标识、时间戳等。扫描识别:用户…

设置和取消Excel“打开密码”的3种方法

在日常工作中&#xff0c;Excel文件中常常包含敏感数据。为了防止未经授权的访问&#xff0c;给Excel文件设置打开密码是一个非常有效的方法。下面分享3种设置Excel打开密码的方法&#xff0c;以及如何取消这些密码。 先来看看设置Excel打开密码的3种方法。 方法一&#xff1…

hnust 1815: 算法10-6~10-8:快速排序

hnust 1815: 算法10-6~10-8&#xff1a;快速排序 题目描述 快速排序是对起泡排序的一种改进。它的基本思想是&#xff0c;通过一趟排序将待排序的记录分割成两个独立的部分&#xff0c;其中一部分记录的关键字均比另一部分的关键字小&#xff0c;在分成两个部分之后则可以分别…

【人工智能】--强化学习(2.0)

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;强化学习与有监督学习的区别 &#x1f348;数据特点 &#x1f348;学习目标 &#x1f348;反馈机制 &#x1f348;策略…

企业级-工具

作者&#xff1a;fyupeng 技术专栏&#xff1a;☞ https://github.com/fyupeng 项目地址&#xff1a;☞ https://github.com/fyupeng/distributed-blog-system-api 留给读者 相信大家在工作中都会遇到各种各种繁琐、简单但耗时的问题&#xff0c;那么可能有以下的工具可以解决你…

代理模式和Java中的动态代理【开发实践】

文章目录 一、代理模式基础1.1 代理模式1.2 静态代理1.3 动态代理 二、静态代理的实现三、JDK动态代理3.1 JDK动态代理概述3.2 invoke方法介绍3.3 JDK动态代理的使用 四、CGLIB动态代理3.1 CGLIB动态代理概述3.2 CGLIB动态代理的使用 五、对比5.1 代理实现与使用对比5.2 使用条…

打赢网络免疫升级战!看聚铭铭察高级威胁检测系统如何重塑网络安全防线

在信息洪流的今天&#xff0c;企业如航行于暗礁密布的数字海洋&#xff0c;面对的不仅仅是已知的病毒与漏洞&#xff0c;更有高级威胁这股暗流&#xff0c;悄无声息地侵蚀着网络的肌理。常规的安全措施&#xff0c;犹如常规体检&#xff0c;虽能捕捉表面的异常&#xff0c;却难…

jQuery 入门到精通

jQuery 入门到精通&#xff1a;详尽指南 目录 jQuery 简介jQuery 基础 安装 jQueryjQuery 选择器DOM 操作事件处理 jQuery 进阶 动画效果AJAX插件 项目实践 简单的待办事项列表获取和显示 API 数据 高级技巧 性能优化调试技巧 资源和总结 1. jQuery 简介 jQuery 是一个快速、…

C++ 的常见算法 之一

C 的常见算法 之一 不修改序列算法for_eachcountfind 修改序列算法copymove 不修改序列算法 for_each #include <iostream> // std::cout #include <algorithm> // std::for_each #include <vector> // std::vectorusing namespace std;struc…

Rust:常见 GUI 框架

Rust 语言因其性能、安全性和并发能力而广受欢迎&#xff0c;尽管它在标准库中不包含直接的图形用户界面&#xff08;GUI&#xff09;工具&#xff0c;但社区已经开发了多个高质量的第三方库来帮助开发者创建 GUI 应用程序。以下是一些流行的 Rust GUI 框架和工具&#xff1a; …

【论文速读】|FuzzAug:探索模糊测试作为神经网络测试生成的数据增强

本次分享论文&#xff1a;FuzzAug: Exploring Fuzzing as Data Augmentation for Neural Test Generation 基本信息 原文作者&#xff1a;Yifeng He, Jicheng Wang, Yuyang Rong, Hao Chen 作者单位&#xff1a;University of California, Davis 关键词&#xff1a;软件测试…

【电控笔记6.5】标准二阶系统

标准二阶系统通常用于描述动态系统的行为,特别是在控制系统、振动系统和其他物理系统中。标准二阶系统的传递函数通常表示为: [ H(s) = \frac{\omega_n2}{s2 + 2\zeta\omega_n s + \omega_n^2} ] 其中,(\omega_n) 是系统的自然频率,(\zeta) 是阻尼比。 标准二阶系统的特…

使用随机生成的随机数过程中,保存数据到数组中 出现很多null

如果 randomId 是一个较大的数字&#xff0c;那么会在 temp 数组中留下很多空位。可能会导致很多 null 值。将 temp 从数组改为对象&#xff0c;以避免稀疏数组的问题。 稀疏数组&#xff1a;当一个数组中大部分元素为0&#xff0c;或者为同一值&#xff08;也就是说可以不是0…

【鸿蒙学习笔记】Image迭代完备

Image Image($r(app.media.zhibo)).width(96) // 图片宽度.height(96) // 图片高度.borderRadius(12) // 图片圆曲度.objectFit(ImageFit.Fill) // 不明objectFit Column({ space: 20 }) {Row() {Image($r(app.media.startIcon)).width(66).height(66).borderRadius(12)}.bac…

Android SurfaceFlinger——创建EGLSurface(二十三)

我们知道 EGL 就是适配 Android 本地窗口系统和 OpenGL ES 的桥接层,OpenGL ES 定义了平台无关的 GL 绘图指令,EGL 则定义了控制 Displays、Contexts 以及 Surfaces 的统一的平台接口。前面我们已经介绍了 Surface 的相关内容,而对于 EGL 来说同样存在一个与之对应的 Surfac…