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

Qt项目中使用 CmdManager 实现高效的命令分发机制


一、简介

在嵌入式、网络通信、串口通信、插件系统等场景中,通常会接收到各种命令编号(Command ID),并根据编号执行不同的逻辑处理。

传统做法是使用 switchif-else 语句来判断命令类型并调用对应函数。但这种方式维护困难,尤其当命令数量增多时,会造成代码臃肿、不易扩展。

为了解决这一问题,我们设计了一个统一的命令分发器 —— CmdManager,它通过 注册回调函数 + 执行分发命令 的方式,提高了系统的模块化程度和可维护性。


二、适用场景

  • 串口/网络命令处理
  • 多模块插件调用
  • UI 控件消息映射
  • 工业控制指令分发
  • 模块解耦设计

三、核心设计理念

✅ 单例模式(Singleton)

为了确保命令管理器在全局中唯一,使用了单例设计模式。
使用方式如下:

CmdManager::getInstance()

✅ 命令回调映射表

我们使用两个 unordered_map 来管理命令与函数的绑定关系:

std::unordered_map<uint8_t,  std::function<void(QMainWindow*, const QByteArray&)>> singleCallbacks;
std::unordered_map<uint16_t, std::function<void(QMainWindow*, const QByteArray&)>> doubleCallbacks;
  • singleCallbacks: 用于存储 单命令(8位) 回调,适合对设备、模块分类。
  • doubleCallbacks: 用于存储 双命令(16位) 回调,适合精细控制。

✅ 自动注册宏

为了让回调注册更加简洁,我们提供了两个宏:

REGISTER_SINGLE_CMD(cmdId, func);  // 注册 8位命令回调
REGISTER_DOUBLE_CMD(cmdId, func);  // 注册 16位命令回调

使用这个宏后,每次程序启动时,命令就会自动注册。


四、源码结构与注释说明

文件:CmdManager.h

以下是简化注释结构的核心定义:

class CmdManager {
public:static CmdManager& getInstance(); // 获取单例实例void registerSingleCommand(uint8_t cmdId, std::function<void(QMainWindow*, const QByteArray&)> callback);void registerDoubleCommand(uint16_t cmdId, std::function<void(QMainWindow*, const QByteArray&)> callback);void executeCommand(uint16_t cmdId, QMainWindow* context, const QByteArray& data);private:CmdManager() {} // 私有构造~CmdManager() {}CmdManager(const CmdManager&) = delete;CmdManager& operator=(const CmdManager&) = delete;std::unordered_map<uint8_t, std::function<void(QMainWindow*, const QByteArray&)>> singleCallbacks;std::unordered_map<uint16_t, std::function<void(QMainWindow*, const QByteArray&)>> doubleCallbacks;
};

完整代码:

#ifndef CMDMANAGER_H
#define CMDMANAGER_H#include <functional>
#include <unordered_map>
#include <string>
#include <QByteArray>
#include <QMainWindow>/*** @brief 命令管理器 CmdManager** 用于注册和分发命令回调函数(可处理单命令和双命令)*/
class CmdManager {
public:/// 获取 CmdManager 单例实例static CmdManager& getInstance() {static CmdManager instance;return instance;}/// 注册单命令(使用 8 位 cmdId,高 8 位有效)void registerSingleCommand(uint8_t cmdId, std::function<void(QMainWindow*, const QByteArray&)> callback) {singleCallbacks[cmdId] = callback;}/// 注册双命令(使用完整的 16 位 cmdId)void registerDoubleCommand(uint16_t cmdId, std::function<void(QMainWindow*, const QByteArray&)> callback) {doubleCallbacks[cmdId] = callback;}/// 执行命令(优先匹配单命令,然后是双命令)void executeCommand(uint16_t cmdId, QMainWindow* context, const QByteArray& data) {// 提取高 8 位尝试匹配单命令uint8_t singleCmd = static_cast<uint8_t>((cmdId >> 8) & 0xFF);auto singleIt = singleCallbacks.find(singleCmd);if (singleIt != singleCallbacks.end()) {singleIt->second(context, data);return;}// 尝试完整匹配双命令auto doubleIt = doubleCallbacks.find(cmdId);if (doubleIt != doubleCallbacks.end()) {doubleIt->second(context, data);return;}// 命令未找到printf("未找到命令: 0x%04X\n", cmdId);}private:CmdManager() {}~CmdManager() {}CmdManager(const CmdManager&) = delete;CmdManager& operator=(const CmdManager&) = delete;std::unordered_map<uint8_t, std::function<void(QMainWindow*, const QByteArray&)>> singleCallbacks;std::unordered_map<uint16_t, std::function<void(QMainWindow*, const QByteArray&)>> doubleCallbacks;
};/// 自动注册单命令宏(在 cpp 文件中使用)
#define REGISTER_SINGLE_CMD(cmdId, func) \static bool _single_##cmdId##_registered = []() -> bool { \CmdManager::getInstance().registerSingleCommand(cmdId, func); \return true; \}();/// 自动注册双命令宏(在 cpp 文件中使用)
#define REGISTER_DOUBLE_CMD(cmdId, func) \static bool _double_##cmdId##_registered = []() -> bool { \CmdManager::getInstance().registerDoubleCommand(cmdId, func); \return true; \}();/// ===================================================================
/// ✅ 使用说明(HOW TO USE)
/// ===================================================================
/// 1. 创建处理函数:
/// void onMyCommand(QMainWindow* w, const QByteArray& data) {
///     qDebug() << "执行命令,数据为:" << data;
/// }
///
/// 2. 在 cpp 文件中注册命令(可放在文件顶部):
/// REGISTER_SINGLE_CMD(0x01, onMyCommand);       // 注册 8 位命令(高 8 位)
 REGISTER_DOUBLE_CMD(0x1234, onMyCommand);    // 注册 16 位命令(完整匹配)
///
/// 3. 执行命令(如从串口/网络接收到 cmdId 和 data):
/// CmdManager::getInstance().executeCommand(0x0100, mainWindow, QByteArray("test"));
///
/// ✅ executeCommand() 会自动优先查找单命令(cmdId >> 8),
/// 若找不到,再查找完整双命令(完整 cmdId)
///
/// 建议命令规范:
/// - 单命令:适用于类似功能分类(如设备类型 0x01、0x02)
/// - 双命令:用于精细操作(如设置某参数 0x1234)
/// ===================================================================#endif // CMDMANAGER_H

五、如何使用(开发者指南)

✅ 1. 定义命令处理函数

void onHelloCmd(QMainWindow* window, const QByteArray& data) {qDebug() << "收到 Hello 命令,数据是:" << data;
}

函数签名必须是:

void function(QMainWindow* window, const QByteArray& data)

你可以在里面访问 UI、解析数据等。


✅ 2. 注册命令回调(推荐写在 .cpp 文件全局作用域中)

REGISTER_SINGLE_CMD(0x01, onHelloCmd);       // 单命令,使用高8位
REGISTER_DOUBLE_CMD(0x1234, onHelloCmd);     // 双命令,全16位匹配

注意:宏在启动时自动运行,确保回调被注册。


✅ 3. 执行命令(通常在接收到数据时)

QByteArray data("Hello from device");
CmdManager::getInstance().executeCommand(0x0100, this, data);  // 会调用 onHelloCmd

流程说明:

  • 优先使用高 8 位查找 singleCallbacks
  • 若未找到,再使用完整 16 位查找 doubleCallbacks
  • 都未匹配到则提示 “未找到命令”。

六、示例代码(完整)

// main.cpp
#include "CmdManager.h"
#include <QApplication>
#include <QMainWindow>
#include <QDebug>void handleHello(QMainWindow* w, const QByteArray& d) {qDebug() << "处理 Hello 命令:" << d;
}void handleCustom(QMainWindow* w, const QByteArray& d) {qDebug() << "处理自定义命令:" << d;
}REGISTER_SINGLE_CMD(0x01, handleHello);
REGISTER_DOUBLE_CMD(0x1234, handleCustom);int main(int argc, char *argv[]) {QApplication app(argc, argv);QMainWindow w;w.show();CmdManager::getInstance().executeCommand(0x0100, &w, "测试Hello命令");CmdManager::getInstance().executeCommand(0x1234, &w, "测试自定义命令");return app.exec();
}

输出:

处理 Hello 命令: "测试Hello命令"
处理自定义命令: "测试自定义命令"

七、优势总结

优点描述
✅ 高扩展性新增命令无需修改旧代码
✅ 解耦设计回调与处理逻辑完全独立
✅ 自动注册无需写 init 函数,减少出错
✅ 模块可维护每个命令可放到独立源文件中
✅ 多种匹配模式支持 8 位分类/16 位精确控制

结语

通过 CmdManager,你可以将原本繁琐的 switch-caseif-else 命令逻辑,变成更清晰、更易维护的“注册-执行”模式,大大提升项目质量。它是 Qt/C++ 项目中通信、插件、控制系统的利器。

http://www.xdnf.cn/news/13021.html

相关文章:

  • 国际上与麦角硫因相关的人体功效试验文献分享
  • 使用homeassistant 插件将tasmota 接入到米家
  • mysql8.0忘记root密码情况下修改密码
  • VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
  • 十一、MySQL 事务底层与高可用原理
  • 基于PSO与BP神经网络回归模型的特征选择实战(Python实现)
  • MySQL--慢查询日志、日志分析工具mysqldumpslow
  • Java多线程实现之Runnable接口深度解析
  • SQLSERVER-DB操作记录
  • PyTorch学习路径与基础实践指南
  • window 显示驱动开发-如何查询视频处理功能(二)
  • SAM2Long本地部署,视频分割处理,绿幕抠像,超长视频支持
  • 【JavaSE】多线程基础学习笔记
  • 第二章 感知机
  • Logistics | 盘盈盘亏与报溢报损
  • FastAPI核心解密:深入“路径操作”与HTTP方法,构建API的坚实骨架
  • Unity-ECS详解
  • 北京智乐活科技有限公司 适趣ai 二面 全栈
  • 比较数据迁移后MySQL数据库和openGauss数据仓库中的表
  • tomcat指定使用的jdk版本
  • STM32使用水位传感器
  • React入门第一步:如何用Vite创建你的第一个React项目?
  • Excel 怎么让透视表以正常Excel表格形式显示
  • 旋量理论:刚体运动的几何描述与机器人应用
  • 认识电子元器件---高低边驱动
  • python数据结构和算法(1)
  • 为什么要创建 Vue 实例
  • Xcode 16 集成 cocoapods 报错
  • 从零手写Java版本的LSM Tree (七):压缩策略
  • VUE3 ref 和 useTemplateRef