# 窗口消息队列 **Repository Path**: wu_wenbo/window-message-queue ## Basic Information - **Project Name**: 窗口消息队列 - **Description**: 基于C++11实现窗口消息队列 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-01-08 - **Last Updated**: 2025-01-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 消息队列框架 * log 日志 * manager 线程管理函数 C函数-threadsafe * router 消息路由 发布订阅模型 发布者线程 订阅者线程 * task 任务封装 * timer 定时器线程 # 一、发布订阅模型 ## 1.发布者-订阅者抽象类 ```c++ //发布者 class Publisher { public: virtual ~Publisher() = default; virtual void subscribe(const Topic& topic, Subscriber* subscriber) = 0; virtual void unsubscribe(const Topic& topic, Subscriber* subscriber) = 0; virtual void publish(const Topic& topic, const Message& message) = 0; }; ``` ```c++ //订阅者 class Subscriber { public: virtual ~Subscriber() = default; virtual void update(const Topic& topic, const Message& message) = 0; }; ``` ## 2.主题和消息 主题可以为字符串或其他类型 消息包含事件类型-窗口事件,定时器事件,用户事件,也可扩展其他事件 ```c++ using Topic = std::string; using Message = tagMessage; struct tagMessage { Event event; //事件类型 std::string msg; //发送的消息 WindowMsg windowMsg; //窗口事件传递的消息 TimerMsg timerMsg; //定时器事件传递的消息 int extend; //扩展信息 void* sender = nullptr; //消息的发送者 void* dest = nullptr; }; //具体的事件类型 enum class Event { Window = 0, // 窗口消息 Timer, // 定时器消息 User, // 用户消息 }; //窗口消息 struct WindowMsg { int Type; int Parameter; char* Info; int hSrcWin; int hTagWin; }; //定时器消息 struct TimerMsg { std::function task; }; ``` ## 3.消息路由-发布者实现类 单例对象,订阅者订阅发布者的主题 维护一个消息队列, 创建一个线程,从消息队列中取出消息,分发不同主题的消息给订阅者 ```c++ #if defined(qMessage) #undef qMessage #endif #define qMessage (static_cast(&Router::getInstance())) //具体的发布者-单例对象 class Router : public Publisher { public: // 获取单例实例 static Router& getInstance() { static Router instance; return instance; } void subscribe(const Topic& topic, Subscriber* subscriber) override; void unsubscribe(const Topic& topic, Subscriber* subscriber) override; void publish(const Topic& topic, const Message& message) override; void start(); private: Router(); ~Router(); Router(const Router&) = delete; Router& operator=(const Router&) = delete; Router(Router&&) = delete; Router& operator=(Router&&) = delete; // 不断的从消息队列中取出消息发布给订阅者 void processMessages(); private: std::map> subscribers_; //存储主题到订阅者列表的映射 std::queue> messageQueue_; //存储主题到消息队列的映射 std::mutex mutex_; std::condition_variable cv_; std::thread thread_; bool stop_ = false; bool threadStarted_ = false; }; Router::Router() : stop_(false), threadStarted_(false) { // 启动一个独立的线程来处理消息队列中的消息 thread_ = std::thread(&Router::processMessages, this); //阻塞当前线程,等待子线程函数启动 { std::unique_lock lock(mutex_); cv_.wait(lock, [this] {return threadStarted_; }); } } ``` ## 4.子任务-订阅者实现类-核心 子任务是消息队列框架任务处理的核心实现 子任务独立线程维护一个消息队列,不断从消息队列中处理对应的消息 维护一个窗口链表,用来处理各个状态 子任务的核心机制是获取消息,无消息时阻塞,根据消息类型分发处理消息 GetMessage: 从messageQueue获取消息 DispatchMessage:根据消息事件类型进行相应的处理 ```c++ class SubTask : public Subscriber { public: explicit SubTask(std::string name, Publisher* publisher); ~SubTask(); // 设置入口函数地址 template inline void setEntryFunction(Func&& func, Args&&... args) { entry_ = std::bind(std::forward(func), std::forward(args)...); } // 订阅主题 void subscribeTopic(Topic t); // 取消订阅主题 void unsubscribeTopic(Topic t); // 开启线程 void start(); // 关闭线程 void stop(); // 获取线程id std::thread::id getThreadId(); // 更新消息 void update(const Topic& t, const Message& m) override; private: // 工作函数 int run(); // 检索线程消息队列中的消息 // > 0 成功获取到消息 0 表示接收到WM_QUIT消息,应用程序应该退出 <0 表示发生了错误 int GetMessage(Topic* t, Message* m); // 分发消息 void DispatchMessage(Topic t, Message m); // 消息入队 void enqueueMessage(const Topic& t, const Message& m); private: std::string name_; std::mutex mutex_; std::condition_variable cv_; std::queue> messageQueue_; std::thread thread_; std::atomic stop_ = false; std::atomic threadStarted_ = false; Publisher* publisher_ = nullptr; std::list topics_; // 增加主题列表 std::function entry_; // 任务入口函数及地址 std::thread::id threadId_; public: // 注册窗口 bool registerWindow(Window* window); // 打开窗口 void openWindow(Window* window); // 注销窗口 bool logoutWindow(); // 关闭窗口 void closeWindow(); //向当前活动窗口发送消息 void activeCurrentWindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin); //向母窗口发送消息 void activeFirstWindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin); // 获取当前活动窗口 Window* getCurrentWindow(); // 获取当前母窗口 Window* getFirstWindow(); private: std::list windows_; // 窗口集合 std::mutex rwMutex_; //变量读写锁 }; ``` ### 线程主体 ```c++ // 函数执行的主体 int SubTask::run() { { std::lock_guard lock(mutex_); qLog->setCurrentThreadName(this->name_); threadStarted_ = true; } cv_.notify_all(); // 通知主线程线程已经启动 // 休眠100ms std::this_thread::sleep_for(std::chrono::milliseconds(100)); LOG("entry_"); if (entry_ != nullptr) { entry_(); } Topic t; Message m; while (GetMessage(&t, &m) != 0) { // 分发消息 DispatchMessage(t, m); } LOG("run end"); return 0; } ``` ### GetMessage():无消息时阻塞 ```c++ // 检索线程消息队列中的消息 // > 0 成功获取到消息 // 0 表示接收到WM_QUIT消息,应用程序应该退出 // <0 表示发生了错误 int SubTask::GetMessage(Topic* t, Message* m) { Topic topic; Message message; { std::unique_lock lock(mutex_); // 等待直到消息队列中有消息或停止标志被设置 cv_.wait(lock, [this] { return !messageQueue_.empty() || stop_.load(); }); if (stop_.load() && messageQueue_.empty()) { LOG("stop------"); return 0; } topic = messageQueue_.front().first; message = messageQueue_.front().second; messageQueue_.pop(); } LOG("message:%s", message.msg.data()); if (t != nullptr) { *t = topic; } if (m != nullptr) { *m = message; } return 1; } ``` ### DispatchMessage():分发处理消息 根据事件类型驱动对应的策略进行相应的处理动作 ```c++ void SubTask::DispatchMessage(Topic t, Message m) { // 获取根据消息的目标窗口进行派发 { std::lock_guard locker(mutex_); if (windows_.empty()) return; } LOG("DispatchMessage -> Topic:%s,Message%s", t.data(), m.msg.data()); switch (m.event) { case Event::Window: //窗口事件 LOG("Event::Window"); // deal window event break; case Event::Timer: // 定时器事件 LOG("Event::Timer"); m.timerMsg.task(); break; case Event::User: // 用户事件 LOG("Event::User"); break; default: break; } } ``` ### 窗口管理 ```c++ // 注册窗口 bool registerWindow(Window* window); // 打开窗口 void openWindow(Window* window); // 注销窗口 bool logoutWindow(); // 关闭窗口 void closeWindow(); //向当前活动窗口发送消息 void activeCurrentWindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin); //向母窗口发送消息 void activeFirstWindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin); // 获取当前活动窗口 Window* getCurrentWindow(); // 获取当前母窗口 Window* getFirstWindow(); private: std::list windows_; // 窗口集合 std::mutex rwMutex_; //变量读写锁 ``` std::list windows_第一个窗口即为当前任务的母窗口,不允许关闭,注册窗口,关闭窗口等操作都是对窗口链表进行添加和移除操作 向窗口发送消息的实质就是**根据链表获取窗口主体进行操作函数调用** # 二、定时器框架 ## 定时器 * 定时器具备唯一ID * 定时器支持可变参数传入 * 定时器执行回调时根据定时器需要执行的线程id决定是在定时器线程执行还是将任务作为定时器事件投递到对应的线程执行 ```c++ class Timer { public: template explicit Timer(std::chrono::milliseconds interval, int repeat, Func&& func, Args&&... args) : task_(std::bind(std::forward(func), std::forward(args)...)), interval_(interval), repeat_(repeat), threadId_(std::thread::id()), id_(nextId()) {} ~Timer() = default; // 定时器执行动作 void execute(); // 获取定时器ID int getId() const; // 当前定时器是否重复执行 bool shouldRepeat() const; // 减少重复次数 void reduceRepeat(); // 获取定时器间隔 std::chrono::milliseconds getInterval() const; // 设置定时器执行的线程 void setThreadId(std::thread::id threadId); private: // 获取定时器唯一ID static int nextId() { static int id = 0; return ++id; } private: std::function task_; std::chrono::milliseconds interval_; int repeat_; int id_; std::thread::id threadId_; //要求任务执行的线程id 0表示立即执行 }; void Timer::execute() { if (threadId_ == std::thread::id()) // 普通定时器直接在当前线程执行 { task_(); } else { //窗口定时器,则将task post到指定线程执行 int taskId = qManager->getTaskId(threadId_); Topic t = TaskThread::getTaskTopic(taskId); Message m; m.event = Event::Timer; m.timerMsg.task = task_; qMessage->publish(t, m); } } ``` ## 定时器管理线程 * 单例模式,对外接口为qTimer * 定时器类型分为窗口定时器及普通定时器,区别在于回调函数执行的环境 * 普通定时器:回调函数在定时器线程执行,注意此时定时器线程所需要使用的资源 * 窗口定时器:回调函数要在对应窗口所在的线程执行,将待执行的函数打包为定时器事件投递至对应线程 * 定时器管理线程维护std::multimap timers_,multimap支持key值相同,key值为到期时间 * 模版函数必须定义为内联函数,因为模版函数是在编译器执行的,必须在头文件中才能编译 ```c++ #if defined(qTimer) #undef qTimer #endif #define qTimer (static_cast(&TimerManager::getInstance())) #define REPEAT_FOREVER -1 #define REPEAT_ONETIME 1 // 基于时间线的定时器管理单例类 class TimerManager { public: // 获取单例实例 static TimerManager& getInstance() { static TimerManager instance; return instance; } // 添加定时器,返回定时器唯一ID template inline int addTimer(std::chrono::milliseconds interval, int repeat, Func&& func, Args&&... args) { Timer timer(interval, repeat, std::forward(func), std::forward(args)...); int timerId = timer.getId(); // 提前计算 timerId { std::lock_guard lock(mutex_); auto expirationTime = std::chrono::steady_clock::now() + interval; timers_.insert({ expirationTime, {timerId, nullptr, timer} }); } cv_.notify_one(); // 通知一个等待的线程 return timerId; } // 添加窗口定时器,返回定时器唯一ID template inline int addWindowTimer(std::chrono::milliseconds interval, int repeat, void* window, Func&& func, Args&&... args) { Timer timer(interval, repeat, std::forward(func), std::forward(args)...); timer.setThreadId(std::this_thread::get_id()); int timerId = timer.getId(); // 提前计算 timerId { std::lock_guard lock(mutex_); auto expirationTime = std::chrono::steady_clock::now() + interval; timers_.insert({ expirationTime, {timerId, window, timer} }); } cv_.notify_one(); // 通知一个等待的线程 return timerId; } template inline int addWindowTimer( int interval, int repeat, void* window, Func&& func, Args&&... args) { Timer timer(std::chrono::milliseconds(interval), repeat, std::forward(func), std::forward(args)...); timer.setThreadId(std::this_thread::get_id()); int timerId = timer.getId(); // 提前计算 timerId { std::lock_guard lock(mutex_); auto expirationTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(interval); timers_.insert({ expirationTime, {timerId, window, timer} }); } cv_.notify_one(); // 通知一个等待的线程 return timerId; } // 复位定时器 void resetTimer(int id); void resetTimer(void* window); // 移除指定定时器,根据唯一ID进行遍历移除 void removeTimer(int id); // 移除窗口下的定时器 void removeTimer(void* window); // 设置指定定时器工作的线程tid void setTimerThreadId(int id, std::thread::id tid); // 启动 void start(); private: TimerManager(); ~TimerManager(); // 删除默认构造函数 TimerManager(const TimerManager&) = delete; TimerManager& operator=(const TimerManager&) = delete; TimerManager(TimerManager&&) = delete; TimerManager& operator=(TimerManager&&) = delete; // 定时器线程work void run(); private: struct TimerEntry { int id; void* window; Timer timer; }; std::multimap timers_; //multimap支持key值相同 std::thread thread_; std::mutex mutex_; std::condition_variable cv_; bool stop_ = false; bool threadStarted_ = false; }; ``` 程序主体 ```c++ // 定时器线程work void TimerManager::run() { qLog->setCurrentThreadName("TimerManager"); { std::lock_guard lock(mutex_); threadStarted_ = true; } cv_.notify_all(); // 通知主线程已经启动 while (true) { std::unique_lock lock(mutex_); // 使用谓词来判断是否继续等待 cv_.wait(lock, [this] { return stop_ || !timers_.empty(); }); // 如果 stop_ 为 true 且 timers_ 为空,则退出循环 if (stop_ && timers_.empty()) { break; } auto now = std::chrono::steady_clock::now(); if (!timers_.empty()) { auto it = timers_.begin(); // 判断第一个超时时间是否到达,第一个是到达时间最早的 if (it->first > now) { // 条件变量等待到最早的到期时刻,在等待过程中会释放lock,如果此时添加了一个新定时器,且新的定时器的到期时间小于当前等待的到期时间,则cv会唤醒当前线程,当前线程重新获取锁,线程重新进入while循环,并重新计算当前最早的定时器(timers_.begin()),此时会处理更早的定时器而不是等到原来的时间点 cv_.wait_until(lock, it->first); } else { Timer timer = it->second.timer; void* window = it->second.window; timers_.erase(it); timer.reduceRepeat(); if (timer.shouldRepeat()) { // 获取执行完成后的当前时刻,获取下一个到期时间 auto newNow = std::chrono::steady_clock::now(); auto newExpirationTime = newNow + timer.getInterval(); timers_.insert({ newExpirationTime, {timer.getId(), window, timer } }); } lock.unlock(); // 使用try catch 如果执行过程中是否出现异常可以进行异常处理 try { timer.execute(); } catch (...) { // 处理异常,确保不影响其他定时器 // 可选:记录日志或采取其他措施 } } } } } ``` multimap是有序容器,判断第一个定时的到期时间是否到达,当前线程阻塞到第一个定时器到期的时刻。 Q: **如果在线程等待第一个定时器到达时又插入了更早时刻到期的定时器是如何处理的呢?** A: 条件变量等待到最早的到期时刻,在等待过程中会释放lock,如果此时添加了一个新定时器,且新的定时器的到期时间小于当前等待的到期时间,则cv会唤醒当前线程,当前线程重新获取锁,线程重新进入while循环,并重新计算当前最早的定时器(timers_.begin()),此时会处理更早的定时器而不是等到原来的时间点 Q:**为什么重复定时器的到期时间在到期时就计算了,而不是在任务执行完成后重新计算?** A:这个地方需要牵涉到一个潜在的问题点,如果在任务执行后再重新计算时间如下图所示 ```c++ Timer timer = it->second.timer; void* window = it->second.window; timers_.erase(it); lock.unlock(); // 使用try catch 如果执行过程中是否出现异常可以进行异常处理 try { timer.execute(); } catch (...) { // 处理异常,确保不影响其他定时器 // 可选:记录日志或采取其他措施 } if (timer.shouldRepeat()) { std::unique_lock relock(mutex_); // 获取执行完成后的当前时刻,获取下一个到期时间 auto newNow = std::chrono::steady_clock::now(); auto newExpirationTime = newNow + timer.getInterval(); timers_.insert({ newExpirationTime, {timer.getId(), window, timer } }); } // 移除指定定时器,根据唯一ID进行遍历移除 void TimerManager::removeTimer(int id) { std::lock_guard lock(mutex_); for (auto it = timers_.begin(); it != timers_.end(); ) { if (it->second.id == id) { it = timers_.erase(it); } else { ++it; } } } ``` 会出现一种情况,因为当前的定时器时刻已经到期已经从map中移除,需要重新计算下个到期时间重新插入map中。 假设我们要处理的是一个窗口定时器,到期后需要将待执行事件包装为定时器事件投递给对应线程处理的,这个地方就牵涉到多线程同步的问题了,假设窗口所在的任务线程立刻执行,此时又在回调函数中执行了了定时器的删除操作,此时在removeTimer方法中就会获得互斥量,当定时器线程执行到插入新的到期时间时就会等待互斥锁释放, 当任务线程执行完成释放互斥量后,定时器线程获得锁添加定时器。此时,本来要在回调函数中移除的定时器又重新插入map队列继续执行!!! 因为定时器线程和任务线程执行的先后顺序不能确定,这个位置就会存在这个隐藏问题,可以采用其他方式比如在回调函数中注册待删除的定时器id,在定时器线程重新注入时检查,但不是个优雅的处理方法,所有采用当前方案,在定时器erase时立刻插入新的定时器,此时互斥量仍未释放,后续任务线程的操作就不会影响 ## 普通定时器 回调函数在定时器线程执行 ```c++ // 添加定时器,返回定时器唯一ID template inline int addTimer(std::chrono::milliseconds interval, int repeat, Func&& func, Args&&... args) { Timer timer(interval, repeat, std::forward(func), std::forward(args)...); int timerId = timer.getId(); // 提前计算 timerId { std::lock_guard lock(mutex_); auto expirationTime = std::chrono::steady_clock::now() + interval; timers_.insert({ expirationTime, {timerId, nullptr, timer} }); } cv_.notify_one(); // 通知一个等待的线程 return timerId; } ``` ## 窗口定时器 回调函数在窗口所在的任务线程执行,窗口定时器需要设定当前线程的tid ```c++ // 添加窗口定时器,返回定时器唯一ID template inline int addWindowTimer(std::chrono::milliseconds interval, int repeat, void* window, Func&& func, Args&&... args) { Timer timer(interval, repeat, std::forward(func), std::forward(args)...); timer.setThreadId(std::this_thread::get_id()); int timerId = timer.getId(); // 提前计算 timerId { std::lock_guard lock(mutex_); auto expirationTime = std::chrono::steady_clock::now() + interval; timers_.insert({ expirationTime, {timerId, window, timer} }); } cv_.notify_one(); // 通知一个等待的线程 return timerId; } ``` # 三、任务窗口管理类 为了管理subTask创建的单例类,对外接口为qManager ```c++ #if defined(qManager) #undef qManager #endif #define qManager (static_cast(&TaskManager::getInstance())) class TaskManager { public: // 获取单例实例 static TaskManager& getInstance() { static TaskManager instance; return instance; } void registerSubscriber(std::thread::id tid, int task, SubTask* subscriber); SubTask* getSubscriber(std::thread::id tid); SubTask* getSubscriber(int task); int getTaskId(std::thread::id tid); // 必须要在主线程调用 void start(); // 注册定时刷新任务 void registerScheduledRefreshTask(int taskId, std::thread::id tid, int interval); private: // tid taskId SubTask std::map> taskMap_; //记录线程id对应的SubTask实例 // tid interval timerId std::map> scheduledTask_; //线程对应的定时器任务 std::mutex mutex_; }; ``` TaskManager记录线程id对应的SubTask实例,函数根据当前所在的线程id找到对应的线程实例,操作对应的线程资源 TaskManager维护一个定时器用于给对应任务线程发送窗口刷新消息 # 四、任务线程类 TaskThread封装了SubTask,和线程的入口函数,TaskThread是创建任务的封装类 ```c++ class TaskThread { public: TaskThread(int taskId, std::string name); ~TaskThread(); template inline void setEntryFunction(Func&& func, Args&&... args) { subscriber_->setEntryFunction(std::forward(func), std::forward(args)...); } void start(); SubTask* getSubscriber(); void setRefreshEventInterval(int interval); int getRefreshEventInterval(int interval); // 获取任务对应的主题 static Topic getTaskTopic(int task); private: int taskId_; std::string name_; SubTask* subscriber_ = nullptr; int refreshEventInterval_ = 1000; //窗口刷新间隔 }; ``` # 五、Manager窗口管理函数 Manager封装线程任务,这些函数必须是线程安全的 核心思想是根据当前函数的线程id寻找对应的实例进行操作,跨线程即跨任务操作采用消息发布方式 ```c++ // 这些函数必须是 thread safe // 注册当前线程的窗口 bool RegisterWindow(Window* window); // 打开当前线程的活动窗口 bool OpenWindow(Window* window); // 关闭当前线程的活动窗口 void CloseWindow(); // 获取当前线程的活动窗口 Window* GetCurrentWindow(); // 线程内通信 void ActiveWindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin); // 跨线程投递消息 0表示投递给当前窗口,1表示投递给母窗口 void postMessage(int task, WindowMsg winMsg, int target = 0); // 设置窗口定时器 int SetWindowTimer(int interval, int repeat, void* win, std::function func); // 移除指定id的定时器 void RemoveTimer(int id); // 复位指定窗口的定时器 void ResetWindowTimer(void* window); // 复位特定窗口定时器 void ResetSpecialTimer(int id); ``` ```c++ bool RegisterWindow(Window* window) { LOG("RegisterWindow"); std::thread::id tid = std::this_thread::get_id(); SubTask* subTask = qManager->getSubscriber(tid); assert(subTask != nullptr); return subTask->registerWindow(window); } bool OpenWindow(Window* window) { LOG("OpenWindow"); std::thread::id tid = std::this_thread::get_id(); SubTask* subTask = qManager->getSubscriber(tid); assert(subTask != nullptr); subTask->openWindow(window); return true; } void CloseWindow() { LOG("CloseWindow"); std::thread::id tid = std::this_thread::get_id(); SubTask* subTask = qManager->getSubscriber(tid); assert(subTask != nullptr); subTask->closeWindow(); } Window* GetCurrentWindow() { LOG("GetCurrentWindow"); std::thread::id tid = std::this_thread::get_id(); SubTask* subTask = qManager->getSubscriber(tid); assert(subTask != nullptr); return subTask->getCurrentWindow(); } // 线程内通讯,不涉及线程的消息投递 void ActiveWindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin) { LOG("ActiveWindowProc"); std::thread::id tid = std::this_thread::get_id(); SubTask* subTask = qManager->getSubscriber(tid); assert(subTask != nullptr); Window* currentWindow = subTask->getCurrentWindow(); assert(currentWindow != nullptr); currentWindow->WindowProc(Type, Parameter, Info, hSrcWin, hTagWin); } //排队消息,一般用于跨线程通信 发给指定任务 target = 0表示发给当前活动窗口 =1表示发给母窗口 void postMessage(int task, WindowMsg winMsg, int target) { LOG("postMessage"); Topic t = TaskThread::getTaskTopic(task); Message m; m.event = Event::Window; //传递窗口消息 m.msg = "postMessage"; m.windowMsg = winMsg; m.extend = target; qMessage->publish(t, m); } /** * @brief 设置定时器 */ int SetWindowTimer(int interval, int repeat, void* win, std::function func) { // 创建一个匿名函数 auto boundFunc = [func]() { func(); }; return qTimer->addWindowTimer(interval, repeat, win, boundFunc); } /** * @brief 移除指定id的定时器 */ void RemoveTimer(int id) { qTimer->removeTimer(id); } /** * @brief 复位指定窗口的定时器 */ void ResetSpecialTimer(int id) { qTimer->resetTimer(id); } /** * @brief 复位指定窗口的定时器 */ void ResetWindowTimer(void* window) { qTimer->resetTimer(window); } ``` # 六、窗口类的使用及任务解耦 ## Window窗口 ```c++ using WinProcPtr = void (*)(int, int, char*, int, int); using WinExitPtr = void (*)(void); struct Window { const char* Name; void* Address; // 函数入口地址 WinProcPtr WindowProc; // 窗口函数 WinExitPtr WindowExit; // 窗口退出函数 bool Active; // 窗口活动标志 int TaskId; // 任务号 int WinNumber; // 窗口号 }; ``` ## 任务窗口组成 ### 1.Window实例 包含窗口的name,入口地址,窗体函数,窗口退出回调函数,窗口活动标志,所属的任务号,窗口号 ```c++ static Window window = { "winMain", &winMain, &WindowProc, &WindowExit, false, MAIN_TASK, 0x0101, }; ``` ### 2.入口函数 ```c++ void winMain(void) { LOG("winMain start"); if (window.Active)return; if (RegisterWindow(&window)) { Initialize(); OpenWindow(&window); /*< Initialize the first open window all variables that you need to used */ sCnt = 0; /*< Set Window Timer, you must set window timer before open another window */ sTimerId = SetWindowTimer(2000, REPEAT_FOREVER, &window, TimerProc); /*< Do something */ ActiveWindowProc(WM_REFRESH, 0, 0, 0, 0); //winPowerOn_SelfTest(0); WindowMsg winMsg; winMsg.Type = WM_CLOSE; postMessage(MOTOR_TASK, winMsg); } } ``` ### 3.窗体函数 处理消息,根据消息类型处理对应的分支 ```c++ static void WindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin) { switch (Type) { case WM_CLOSE: LOG("WM_CLOSE"); break; case WM_REDRAW: Initialize(); OpenWindow(&window); break; case WM_REFRESH: LOG("WM_REFRESH"); break; default: break; } } ``` ### 4.窗口退出回调函数 ```c++ static void WindowExit(void) { } ``` ### 5.窗口定时器 ```c++ static void TimerProc() { LOG("TimerProc -> %d", ++sCnt); RemoveTimer(sTimerId); winPowerOn_SelfTest(1); } ``` ### winMian窗口实例 ```c++ #include "Project.h" /* Private macro ------------------------------------------------------------*/ /* Private variables --------------------------------------------------------*/ static int sTimerId; static int sCnt; /* Private function prototypes ----------------------------------------------*/ static void Initialize(void); static void WindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin); static void WindowExit(void); static void TimerProc(); static Window window = { "winMain", &winMain, &WindowProc, &WindowExit, false, MAIN_TASK, 0x0101, }; static void Initialize(void) { } void winMain(void) { LOG("winMain start"); if (window.Active)return; if (RegisterWindow(&window)) { Initialize(); OpenWindow(&window); /*< Initialize the first open window all variables that you need to used */ sCnt = 0; /*< Set Window Timer, you must set window timer before open another window */ sTimerId = SetWindowTimer(2000, REPEAT_FOREVER, &window, TimerProc); /*< Do something */ ActiveWindowProc(WM_REFRESH, 0, 0, 0, 0); //winPowerOn_SelfTest(0); WindowMsg winMsg; winMsg.Type = WM_CLOSE; postMessage(MOTOR_TASK, winMsg); } } static void WindowProc(int Type, int Parameter, char* Info, int hSrcWin, int hTagWin) { switch (Type) { case WM_CLOSE: LOG("WM_CLOSE"); break; case WM_REDRAW: Initialize(); OpenWindow(&window); break; case WM_REFRESH: LOG("WM_REFRESH"); break; default: break; } } static void WindowExit(void) { } static void TimerProc() { LOG("TimerProc -> %d", ++sCnt); RemoveTimer(sTimerId); winPowerOn_SelfTest(1); } ``` ## 任务解耦的价值 实际工程中我们希望的是一个功能一块尽可能的独立解耦,这样程序改动对其他模块的影响很小。 窗口消息机制实质是将一个个具体的功能分成1234各个步骤每个步骤就是一个窗口 调用这个窗口就是执行功能,窗口执行的结果一般分为成功或失败,任务执行成功,向上一级窗口及调用者返回成功或失败的消息,调用者在窗体函数中接收到这个消息进行处理,可根据结果执行不同的策略,成功执行执行2,不成功执行3 ### 举例说明 一个水泵控制器,由按钮开关,电机驱动器,指示灯,蜂鸣器组成。 需要实现的功能如下: * 打开启动按钮,水泵开启上水,此时指示灯闪烁,超时2min后未收到关闭则停止水泵 * 水泵上水过程中如果点击停止按钮就需要停止退出,指示灯恢复常亮 * 如果水泵开启失败,要反馈故障原因,蜂鸣器报警,指示灯快速闪烁 **分析**: 我们需要一个主任务用来处理控制器的逻辑定义为MAIN_TASK,一个电机任务MOTOR_TASK用于控制电机 MAIN_TASK的母窗口及待机窗口定义为winMain,负责接收按键消息 MOTOR_TASK的母窗口同样也是待机窗口定义为winMotor,负责控制电机动作 此时的流程如下: **主任务**: 按键处理单元检测到按键按下 ---> ​ 发送消息给MAIN_TASK当前窗口即winMain ---> ​ winMain的窗体函数接收到按键消息后打开winWaterContorl窗口 ---> ​ winWaterContorl窗口向MOTOR_TASK投递开启电机的消息,创建一个单次窗口定时器间隔2min, 控制指示灯闪烁 winWaterContorl窗体接收到开启失败则创建报警码向上一级窗口传递 ​ 收到成功消息则等待超时结束->向MOTOR_TASK发送关闭电机指令 ​ 接收到关闭按钮消息->MOTOR_TASK发送关闭电机指令 ​ -->等待接收电机任务执行状态--->成功or失败,控制指示灯 winMain接收到成功消息,则继续待机 ​ 接收到失败消息,打开winAlarm报警窗口,打开蜂鸣器,指示灯闪烁提示报警,等待复位 ​ **电机任务**: winMotor接收到开启电机消息,打开winMotorAction窗口---> ​ winMotorAction窗口发送控制报文给驱动器--->返回成功or失败给winMotor ---> ​ winMotor接收到winMotorAction的结果反馈给任务发送者 ​