基于RTTR在C++中实现结构体数据的多层级动态读写

embedded/2025/3/15 10:43:10/

文章目录

  • 1.背景
  • 2.RTTR
    • 2.1.注册结构体
    • 2.2.实现读操作
    • 2.3.实现写操作
  • 3.读写调用例程
  • 4.结语

1.背景

目前有个项目,同一台电脑上的codesys程序将其结构体数据通过共享内存的方式写道了一个“共享内存”上。
我在取得内存数据后,需要对这个数据进行结构体的解析,然后才能读取结构体内的变量。
这个没问题,只要和codesys的那边定义的结构体对好数据类型及顺序,那就可以通过读取了。
但是在取得内存数据,且将结构体指针指向此内存后,我想实现一个通过字符串来读取对应结构体的数据的功能,类似解释型语言那种,专业术语叫反射。
这里举个例子。
比如我现在有这些结构体的定义:

struct Point
{int x;int y;
};
struct Line
{Point pt1;Point pt2;
};
struct Face
{Line lines[300];
};
struct Object
{Face faces[100];
};

然后实例化了这个对象,且采用一般的读写

Object obj;
// 一般的读写
obj.faces[0].lines[123].pt1.x = 10;

我要实现的是这样

Object obj;
// 非一般的读写
void *myPtr = getDataPtr(obj, "faces[0].lines[123].pt1.x");*((int*)myPtr) = 10;

2.RTTR

还好,有人写了一个挺好用的库TRRT,基于它,可以实现我所需要的功能。
【RTTR,An open source library, which adds reflection to C++.】

在这里插入图片描述注意,一定要下载0.9.6版本来编译使用。因为这个版本开始才支持policy::prop::as_reference_wrapper,这个功能对于后面实现的写操作有大用。
使用时可能会报未定义,可以手动将下面这个加到你项目中的某个cpp文件中

// 使用 -lrttr_core_s, 缺少下面这些静态定义
namespace rttr
{/const detail::bind_as_ptr policy::prop::bind_as_ptr = {};const detail::as_reference_wrapper policy::prop::as_reference_wrapper = {};const detail::return_as_ptr policy::meth::return_ref_as_ptr = {};const detail::discard_return policy::meth::discard_return = {};/const detail::as_raw_pointer policy::ctor::as_raw_ptr = {};const detail::as_std_shared_ptr policy::ctor::as_std_shared_ptr = {};const detail::as_object policy::ctor::as_object = {};/} // end namespace rttr

2.1.注册结构体

这里需要用相应的代码来将我们自定义的结构注册到RTTR系统。注意,一定要使用policy::prop::as_reference_wrapper

#include <rttr/registration>using namespace rttr;
RTTR_REGISTRATION
{registration::class_<Point>("Point").property("x", &Point::x)(policy::prop::as_reference_wrapper).property("y", &Point::y)(policy::prop::as_reference_wrapper);registration::class_<Line>("Line").property("pt1", &Line::pt1)(policy::prop::as_reference_wrapper).property("pt2", &Line::pt2)(policy::prop::as_reference_wrapper);registration::class_<Face>("Face").property("lines", &Face::lines)(policy::prop::as_reference_wrapper);registration::class_<Object>("Object").property("faces", &Object::faces)(policy::prop::as_reference_wrapper);
}
// policy::prop::as_reference_wrapper
// policy::prop::bind_as_ptr

2.2.实现读操作

RTTR本身是支持单层数据访问的,也就是可以直接访问到结构的的子变量,但是不能直接访问到孙变量及更深层级的变量。
但是,因为我们前面已经指定了变量都用std::reference_wrapper来封装,因此我们可以取得子成员的引用,从未可以对其再取子成员,以此递归,便可以实现我们的目的了:

// 创建一个函数模板,用于将rttr::variant转换为QVariant
template<typename T>
QVariant variantToQVariant(const rttr::variant& var) {T wVal = var.get_wrapped_value<T>();return QVariant(wVal);
}// 创建一个映射,将rttr::type_id映射到对应的转换函数
std::map<rttr::type::type_id, QVariant(*)(const rttr::variant&)> typeConverters = {{ rttr::type::get<std::reference_wrapper<double>>().get_id(), variantToQVariant<double> },{ rttr::type::get<std::reference_wrapper<qint8>>().get_id(),  variantToQVariant<qint8>  },{ rttr::type::get<std::reference_wrapper<qint16>>().get_id(), variantToQVariant<qint16> },{ rttr::type::get<std::reference_wrapper<qint32>>().get_id(), variantToQVariant<qint32> },{ rttr::type::get<std::reference_wrapper<qint64>>().get_id(), variantToQVariant<qint64> },{ rttr::type::get<std::reference_wrapper<bool>>().get_id(),   variantToQVariant<bool> },// { rttr::type::get<std::string>().get_id(),   variantToQVariant<char*> },};QVariant convertVariantToQVariant(const variant &val) {rttr::type::type_id id = val.get_type().get_id();auto it = typeConverters.find(id);if (it != typeConverters.end()) {// 如果找到了对应的转换函数,则调用它return it->second(val);} else {QString typeName = val.get_type().get_name().data();if(typeName.contains("std::reference_wrapper<char[")){return QVariant(QString(val.get_value<std::string>().data()));}else{// 如果没有找到对应的转换函数,可以返回一个默认值qDebug() << "no type" << id << typeName;return QVariant();}}
}variant getValueByPath(variant rootObj, QString path) {if (!rootObj.is_valid()) return variant();QStringList strList = path.split(".");variant obj = rootObj;for(int i = 0; i < strList.length(); i++){QString token = strList.at(i);// 假如是数组int arrayIdx = -1;if(token.endsWith("]")){QStringList tmpStrList = token.split("[");if(tmpStrList.length() == 2){token = tmpStrList[0].replace("[", "");arrayIdx = tmpStrList[1].replace("]", "").toInt();}}type t = obj.get_type();if(t.is_wrapper()){t = t.get_wrapped_type();}property prop = t.get_property(token.toStdString().data());if (!prop.is_valid()) return variant();obj = prop.get_value(obj);t = obj.get_type();// 假如是数组if(arrayIdx != -1){obj = obj.create_sequential_view().get_value(arrayIdx);}}return obj;
}

2.3.实现写操作

前面getValueByPath返回的variant持有的数据是一个std::reference_wrapper类型的,利用其我们可以得到原始数据的引用,要进行写操作的话,直接对这个应用进行赋值操作即可。我这里更进一步,取其原始数据的指针,这个指针有大用,下一篇文章再介绍。

// 创建一个函数模板,用于取原始数据的指针
template<typename T>
void* getSourcePointer(const rttr::variant& var) {return &var.get_value<std::reference_wrapper<T>>().get();
}// 创建一个映射,将rttr::type_id映射到对应的转换函数
std::map<rttr::type::type_id, void*(*)(const rttr::variant&)> pointerGetters = {{ rttr::type::get<std::reference_wrapper<double>>().get_id(),  getSourcePointer<double> },{ rttr::type::get<std::reference_wrapper<qint8>>() .get_id(),  getSourcePointer<qint8>  },{ rttr::type::get<std::reference_wrapper<qint16>>().get_id(),  getSourcePointer<qint16> },{ rttr::type::get<std::reference_wrapper<qint32>>().get_id(),  getSourcePointer<qint32> },{ rttr::type::get<std::reference_wrapper<qint64>>().get_id(),  getSourcePointer<qint64> },{ rttr::type::get<std::reference_wrapper<bool>>()  .get_id(),  getSourcePointer<bool> },};void *RTTRUtils::getValueAddress(variant var)
{type typ = var.get_type();if(typ.is_wrapper() == false){return nullptr;}auto it = pointerGetters.find(typ.get_id());if (it != pointerGetters.end()) {// 如果找到了对应的转换函数,则调用它return it->second(var);} else {QString typeName = var.get_type().get_name().data();if(typeName.contains("std::reference_wrapper<char[")){return var.get_value<std::string&>().data();}else{// 如果没有找到对应的转换函数,可以返回一个默认值或抛出异常qDebug() << "no type, no pointer" << typ.get_name().data();return nullptr;}}
}

3.读写调用例程

下面给出一段代码,演示一下读、写、地址偏移计算。
这个地址偏移计算是我搞了一周时间想要搞到的东西,有了它,和我配合的搞PLC程序的兄弟的代码可以减少100%。
可以看到,无论读、写还是地址偏移计算,都是没有问题的。

        Object obj;obj.faces[0].lines[1].pt1.x = 1122;obj.faces[10].lines[11].pt1.y = 1123;// 读variant var = RTTRUtils::getValueByPath(std::ref(obj), "faces[0].lines[1].pt1.x");qDebug() << "read value:" << RTTRUtils::convertVariantToQVariant(var);// 写void *ptr = RTTRUtils::getValueAddress(var);*(int*)ptr = 100;qDebug() << "write value:" << obj.faces[0].lines[1].pt1.x;// 计算偏移qint64 addrOffset = (qint64)ptr - (qint64)&obj;qDebug() << "size:" << sizeof(Point) << sizeof(Line);qDebug() << "addr Offset:" << addrOffset;

在这里插入图片描述

4.结语

如此一来,在c++中,使用字符串来定位+读写结构体成员数据的操作就实现了。
这个只是基础,如何发挥它真正强大威力,等下一篇再介绍了。预告一下,主要是用共享内存来实现进程间读写PLC变量的。


参考
【C++笔记-RTTR编译&安装&简单使用】


http://www.ppmy.cn/embedded/172739.html

相关文章

第13章贪心算法

贪心算法 局部最优求得总体最优 适用于桌上有6张纸币&#xff0c;面额为100 100 50 50 50 10&#xff0c;问怎么能拿走3张纸币&#xff0c;总面额最大&#xff1f;—拿单位价值最高的 只关注局部最优----关注拿一张的最大值拆解-----拿三次最大的纸币 不适用于桌面三件物品&am…

Linux常用命令速查手册

Linux常用命令速查手册 Linux常用命令速查手册1. 文件和目录操作1.1 查看当前目录&#xff08;pwd&#xff09;1.2 切换目录&#xff08;cd&#xff09;1.3 列出目录内容&#xff08;ls&#xff09;1.4 创建目录&#xff08;mkdir&#xff09;1.5 删除文件和目录&#xff08;rm…

xlua 运行原理

iOS限制App的二进制代码要一次性的包含在App内&#xff0c;也就是AOT&#xff0c;不支持JITLua代码作为资源文件&#xff0c;玩家下载&#xff0c;不涉及字节码&#xff0c;所以可以做热更Lua代码通过Lua虚拟机解释执行&#xff08;解释成机器码&#xff09;&#xff0c;并在虚…

小语言模型(SLM)技术解析:如何在有限资源下实现高效AI推理

引言&#xff1a;为什么小语言模型&#xff08;SLM&#xff09;是2025年的技术焦点&#xff1f; 2025年&#xff0c;人工智能领域正经历一场“由大变小”的革命。尽管大语言模型&#xff08;LLM&#xff09;如GPT-4、Gemini Ultra等在复杂任务中表现惊艳&#xff0c;但其高昂的…

泛目录效果:提升网站SEO与用户体验的关键策略

泛目录效果&#xff1a;提升网站SEO与用户体验的关键策略 在当今数字化时代&#xff0c;网站优化&#xff08;SEO&#xff09;已成为企业提升在线可见性和吸引流量的重要手段。其中&#xff0c;泛目录效果作为一种有效的SEO策略&#xff0c;不仅能够提升搜索引擎排名&#xff…

【6】拓扑排序学习笔记

前言 有向无环图和拓扑排序直接关联到中后期的图论建模思想&#xff0c;是很重要的基础知识。这个如果不彻底弄懂&#xff0c;以后图论会很困难。 有向无环图 正如其名&#xff0c;一个边有向&#xff0c;没有环的图&#xff0c;也叫DAG。 DAG图实际运用&#xff1a;描述含…

STM32项目分享:STM32智能窗户

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; STM32智能窗户 &#xff08;资料分享见文末&…

搜广推校招面经四十九

tiktok广告算法 一、倒排索引原理及Map中Key的处理 具体使用方法见【搜广推校招面经三十六】 倒排索引&#xff08;Inverted Index&#xff09;是信息检索系统中常用的一种数据结构&#xff0c;用于快速查找包含某个关键词的文档。以下是倒排索引的原理及Map中Key的处理方式的…