文章目录
- 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编译&安装&简单使用】