当前位置: 首页 > news >正文

C++实现简化版Qt的QObject(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

相关文章:

  • 视频字幕提取在线工具有哪些?总结5个字幕提取工具
  • three.js地理坐标系有哪些,和屏幕坐标系的转换。
  • layui+jsp项目中实现table单元格嵌入下拉选择框功能,下拉选择框可手动输入内容或选择默认值,修改后数据正常回显。
  • Emp.dll文件丢失?理解Emp.dll重要性与处理常见问题
  • 【NodeJs】入门
  • VuePress 的更多配置
  • 用C语言声明汇编编写的函数,是否需要带参数列表?
  • 格雷码与二进制转换电路设计与仿真
  • 如何通过指纹浏览器使用代理IP?
  • 音视频入门基础:H.264专题(9)——SPS简介
  • cache映射
  • 【python】OpenCV—Feature Detection and Matching
  • 速锐得解码汽车以太网技术特点接口定义数据传输及应用
  • Redis+定式任务实现简易版消息队列
  • 学习笔记——动态路由——OSPF(工作原理)
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • k8s如何管理Pod
  • MySQL主从复制读写分离及奇怪的问题
  • NSTimer学习笔记
  • storm drpc实例
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 翻译:Hystrix - How To Use
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 追踪解析 FutureTask 源码
  • Spring Batch JSON 支持
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 容器镜像
  • # Panda3d 碰撞检测系统介绍
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • #etcd#安装时出错
  • (1) caustics\
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (转)程序员技术练级攻略
  • .Net 4.0并行库实用性演练
  • .NET 6 在已知拓扑路径的情况下使用 Dijkstra,A*算法搜索最短路径
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • .php结尾的域名,【php】php正则截取url中域名后的内容
  • @test注解_Spring 自定义注解你了解过吗?
  • [ 数据结构 - C++] AVL树原理及实现
  • [Android]将私钥(.pk8)和公钥证书(.pem/.crt)合并成一个PKCS#12格式的密钥库文件
  • [BJDCTF2020]The mystery of ip1
  • [C#]扩展方法
  • [CocosCreator]Android的增加AndroidX的动态权限
  • [codevs 1515]跳 【解题报告】
  • [CSS]浮动