伺服设备参数界面valueEdit冲突Bug修复
问题描述
在使用ECAT Master软件管理多个伺服设备时,发现一个严重的bug:当读取一个伺服设备的参数后,其他伺服设备的参数界面也会显示相同的参数值,导致参数显示混乱。

具体现象
- 有3个伺服设备(伺服1、伺服2、伺服3)
- 读取伺服1的参数后,伺服2和伺服3的参数界面也会显示与伺服1相同的参数值
- 每个伺服设备的valueEdit控件显示的值相互影响,无法正确显示各自的参数
根本原因分析
经过代码分析,发现问题的根本原因是多个ServoDrive对象共享同一个XmlFile和ObjectDic对象。
详细分析
- XmlFile对象共享 :在
ServoDrive::setXmlFile和ServoDrive::setDefaultXmlFile方法中,直接将传入的XmlFile指针赋值给当前ServoDrive对象,没有创建深拷贝 - ObjectDic对象共享:每个XmlFile包含一个ObjectDic列表,用于存储参数信息。由于XmlFile被共享,所有ServoDrive对象实际上使用的是同一个ObjectDic列表
- 信号槽连接:每个valueEdit控件通过信号槽连接到ObjectDic的readFinish信号,当一个ObjectDic的值更新时,所有连接到它的valueEdit都会更新
代码问题点
原代码(问题代码)
cpp
// 设置XML文件
void ServoDrive::setXmlFile(XmlFile* xmlFile)
{
if(xmlFile->vendorID == this->Vendor_ID && xmlFile->productCode == this->ProductCode){
this->xmlFile = xmlFile; // 直接赋值,共享同一个XmlFile对象
}
}
// 设置默认XML文件
void ServoDrive::setDefaultXmlFile(XmlFile* xmlFile)
{
this->xmlFile = xmlFile; // 直接赋值,共享同一个XmlFile对象
}
解决方案
核心思路
为每个ServoDrive对象创建独立的XmlFile和ObjectDic对象,确保参数值的存储和更新互不影响。
具体实现
-
修改
ServoDrive::setXmlFile方法:- 创建XmlFile的深拷贝,包括所有属性
- 深拷贝ObjectDic列表,为每个ObjectDic创建独立的拷贝
- 构建快速查找表,确保查找功能正常
-
修改
ServoDrive::setDefaultXmlFile方法:- 同样实现深拷贝逻辑
-
修改
ServoDrive::~ServoDrive析构函数:- 添加对xmlFile的释放逻辑,避免内存泄漏
修复后代码
修改ServoDrive::setXmlFile方法
cpp
/**
* @brief 设置XML文件
* @param xmlFile XML文件对象
* @details 当XML文件的厂商ID和产品代码与伺服匹配时,创建XML文件的深拷贝并设置为当前XML文件
*/
void ServoDrive::setXmlFile(XmlFile* xmlFile)
{
if(xmlFile->vendorID == this->Vendor_ID && xmlFile->productCode == this->ProductCode){
// 创建XmlFile的深拷贝,确保每个ServoDrive有自己独立的ObjectDic对象
XmlFile* copyXmlFile = new XmlFile(this);
copyXmlFile->filename = xmlFile->filename;
copyXmlFile->vendorID = xmlFile->vendorID;
copyXmlFile->productCode = xmlFile->productCode;
copyXmlFile->revisionNo = xmlFile->revisionNo;
copyXmlFile->typeName = xmlFile->typeName;
copyXmlFile->Icon = xmlFile->Icon;
copyXmlFile->bOdicValid = xmlFile->bOdicValid;
// 深拷贝ObjectDic列表
copyXmlFile->oList = new QList<ObjectDic*>();
copyXmlFile->oMap = new QMap<quint32, ObjectDic*>();
if(xmlFile->oList) {
for(ObjectDic* obj : *xmlFile->oList) {
ObjectDic* copyObj = new ObjectDic(copyXmlFile);
copyObj->index = obj->index;
copyObj->subindex = obj->subindex;
copyObj->name = obj->name;
copyObj->type = obj->type;
copyObj->bitSize = obj->bitSize;
copyObj->min = obj->min;
copyObj->max = obj->max;
copyObj->defVal = obj->defVal;
copyObj->curVal = obj->curVal;
copyObj->access = obj->access;
copyObj->Category = obj->Category;
copyObj->checked = obj->checked;
copyXmlFile->oList->append(copyObj);
}
// 构建快速查找表
copyXmlFile->buildObjectMap();
}
this->xmlFile = copyXmlFile;
}
}
修改ServoDrive::setDefaultXmlFile方法
cpp
/**
* @brief 设置默认XML文件
* @param xmlFile XML文件对象
* @details 直接设置为当前XML文件,不进行厂商ID和产品代码的匹配,创建XML文件的深拷贝
*/
void ServoDrive::setDefaultXmlFile(XmlFile* xmlFile)
{
// 创建XmlFile的深拷贝,确保每个ServoDrive有自己独立的ObjectDic对象
XmlFile* copyXmlFile = new XmlFile(this);
copyXmlFile->filename = xmlFile->filename;
copyXmlFile->vendorID = xmlFile->vendorID;
copyXmlFile->productCode = xmlFile->productCode;
copyXmlFile->revisionNo = xmlFile->revisionNo;
copyXmlFile->typeName = xmlFile->typeName;
copyXmlFile->Icon = xmlFile->Icon;
copyXmlFile->bOdicValid = xmlFile->bOdicValid;
// 深拷贝ObjectDic列表
copyXmlFile->oList = new QList<ObjectDic*>();
copyXmlFile->oMap = new QMap<quint32, ObjectDic*>();
if(xmlFile->oList) {
for(ObjectDic* obj : *xmlFile->oList) {
ObjectDic* copyObj = new ObjectDic(copyXmlFile);
copyObj->index = obj->index;
copyObj->subindex = obj->subindex;
copyObj->name = obj->name;
copyObj->type = obj->type;
copyObj->bitSize = obj->bitSize;
copyObj->min = obj->min;
copyObj->max = obj->max;
copyObj->defVal = obj->defVal;
copyObj->curVal = obj->curVal;
copyObj->access = obj->access;
copyObj->Category = obj->Category;
copyObj->checked = obj->checked;
copyXmlFile->oList->append(copyObj);
}
// 构建快速查找表
copyXmlFile->buildObjectMap();
}
this->xmlFile = copyXmlFile;
}
修改ServoDrive::~ServoDrive析构函数
cpp
/**
* @brief 析构函数
* @details 释放资源
*/
ServoDrive::~ServoDrive()
{
// 释放资源
// 释放自己创建的XmlFile对象
if (xmlFile) {
delete xmlFile;
xmlFile = nullptr;
}
}
改动前后对比
| 项目 | 改动前 | 改动后 |
|---|---|---|
| XmlFile对象 | 多个ServoDrive共享同一个XmlFile对象 | 每个ServoDrive有自己独立的XmlFile对象 |
| ObjectDic对象 | 多个ServoDrive共享同一个ObjectDic列表 | 每个ServoDrive有自己独立的ObjectDic列表 |
| 参数值存储 | 所有ServoDrive的参数值存储在同一个ObjectDic中 | 每个ServoDrive的参数值存储在自己的ObjectDic中 |
| 内存管理 | XmlFile由XmlUtil管理,ServoDrive不释放 | ServoDrive在析构时释放自己创建的XmlFile |
| 行为表现 | 读取一个伺服的参数会影响其他伺服的显示 | 每个伺服的参数显示独立,互不影响 |
技术要点
-
深拷贝实现:
- 不仅拷贝XmlFile的基本属性,还要深拷贝其中的ObjectDic列表
- 为每个ObjectDic创建独立的拷贝,确保参数值的存储空间独立
-
快速查找表:
- 为拷贝后的XmlFile重新构建oMap,确保通过index和subindex查找ObjectDic的功能正常
-
内存管理:
- 在ServoDrive析构时释放自己创建的XmlFile对象,避免内存泄漏
- 确保XmlFile的生命周期与ServoDrive一致
-
信号槽机制:
- 每个valueEdit控件连接到对应的ObjectDic的信号
- 由于ObjectDic独立,信号只会影响对应的valueEdit控件
测试验证
测试步骤
- 启动ECAT Master软件
- 连接3个伺服设备
- 读取伺服1的参数
- 切换到伺服2的参数页面,验证显示的是伺服2的参数
- 读取伺服2的参数
- 切换回伺服1的参数页面,验证显示的仍是伺服1的参数
- 重复上述步骤,确保参数显示正确且互不影响
预期结果
- 每个伺服设备的参数页面显示各自的参数值
- 读取一个伺服的参数不会影响其他伺服的参数显示
- 软件运行稳定,无内存泄漏
结论
通过为每个ServoDrive对象创建独立的XmlFile和ObjectDic对象,成功解决了伺服设备参数界面valueEdit冲突的bug。这一修复确保了每个伺服设备的参数值存储和显示独立,提高了软件的可靠性和用户体验。
技术启示
- 在设计多对象共享数据时,应考虑是否需要深拷贝
- 对于包含复杂数据结构的对象,深拷贝是确保数据独立性的重要手段
- 内存管理是C++编程中的关键环节,需要确保对象的正确创建和释放
- 信号槽机制在Qt中是实现组件间通信的强大工具,但需要注意信号的作用范围
后续建议
- 代码优化:可以考虑实现XmlFile的拷贝构造函数,使深拷贝逻辑更加清晰
- 单元测试:为ServoDrive类添加单元测试,确保深拷贝功能正常
- 文档更新:更新相关文档,说明ServoDrive对象的数据独立性
- 性能优化:对于大量参数的情况,可以考虑延迟加载或其他性能优化策略
通过这次bug修复,不仅解决了实际问题,也加深了对Qt对象管理和内存管理的理解。希望这篇文章能对遇到类似问题的开发者有所帮助。