Qt程序基于共享内存读写CodeSys的变量

文章目录

  • 1.背景
  • 2.结构体从CodeSys导出后导入到C++
    • 2.1.将结构体从CodeSys中导出
    • 2.2.将结构体从m4文件提取翻译成c++格式
  • 3.添加RTTR注册信息
  • 4.读取PLC变量值
  • 5.更改PLC变量值
  • 6.Qt读写CodeSys的共享内存

1.背景

在文章【基于RTTR在C++中实现结构体数据的多层级动态读写】中,我们实现了通过字符串读写结构体中的变量。那么接下来我们开始与CodeSys来进行交互。
由于我们是基于共享内存来通讯的,那么我们需要对共享的内存定义一个数据结构,也就是一个结构体。
假如我们和PLC的通讯只是简单的一个结构体,结构体中都是一些POD(Plain Old Data),那可以直接和PLC程序编写人员协商沟通好,让他把结构的定义代码发给你,你再根据ST代码写出结构体的C++代码。
但是,在实际的项目中,要到使用到的结构体往往是多种类型的结构体互相嵌套的结果。不仅结构体多、数据多,而且还存在数组、嵌套的方式,单纯靠手工来拷贝ST代码-》转C++代码必定是繁琐且容易出错的。因此,必须得搞一套稳定可靠的导出导入机制。
这里我选择通过利用CodeSys的机制+python脚本来实现

2.结构体从CodeSys导出后导入到C++

要想将Application的结构体数据直接导出,貌似是不行的,但是可以先把结构体数据复制到一个Library工程,然后导出m4文件,最后利用python脚本翻译(处理)成我们需要的代码。

2.1.将结构体从CodeSys中导出

我这里有一个Application工程,里面定义了若干结构体
在这里插入图片描述

假如想将其导出,那么可以新建一个Library工程,然后将结构体复制过去(直接在左侧的树状列表中选择、复制,而不是直接复制代码)。
在这里插入图片描述
然后选择 编译–》生成运行时系统文件
在这里插入图片描述
然后勾选M4接口文件
在这里插入图片描述
然后点击确定、生成M4文件。
如此,便完成了结构体的导出。

2.2.将结构体从m4文件提取翻译成c++格式

其实打开M4文件看一下,可以发现,导出数据已经是c语言格式的结构体了,基本都可以拿来直接用了,但是由于后面要和RTTR结合使用,必须还得清洗处理一下。
在这里插入图片描述M4文件的清洗处理我们需要用到clang(LLVM)库。
我们是在python中使用clang,因此我们需要在python中安装此工具包,我安装的是20.1.0:
在这里插入图片描述但是在python中安装了还不行,还得去官网将依赖的库及程序文件下载下来
【llvm github】
在这里插入图片描述下载之后,解压到某个路径下即可,不用安装
在这里插入图片描述
然后就可以使用脚本了,这是我的脚本
在脚本中指定好M4文件所在路径、中间文件保存路径、最终文件保存路径,运行即可

import sys
import clang.cindex
from clang.cindex import CursorKind, TypeKind, Config
import os# 前面提到的clang压缩包的解压的路径,根据自己的路径指定
Config.set_library_path("D:/Qt/clang+llvm-20.1.0-x86_64-pc-windows-msvc/bin")# M4文件位置
m4FilePath = r'C:/Users/Administrator/Desktop/stateTest/StructOutputItf.m4'
# 中间文件位置
middleFilePath = "./output/tmpFile.h"
# 处理后的文件位置
outputFilePath = "./output/memorydefine.h"def convert_c_struct_to_cpp(input_file, output_file):index = clang.cindex.Index.create()# tu = index.parse(input_file, args=['-std=c++11'])# Windows特定参数args = ['-finput-charset=UTF-8','-std=c++11','-x', 'c++',  # 强制按C++模式解析# r'-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include',  # MSVC头文件路径# r'-IC:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt',  # Windows SDK路径,r'-IC:\Program Files\CODESYS 3.5.19.60\CODESYS\CODESYS Control SL Extension Package\4.10.0.0\ExtensionSDK\include',# Windows SDK路径,# r'-ID:\Qt5.15\5.15.2\msvc2019_64\include\QtCore',]tu = index.parse(input_file, args=args)struct_defs = []def analyze_typedef(node):if node.kind == CursorKind.TYPEDEF_DECL:canonical_type = node.type.get_canonical()# print("--", canonical_type.spelling)# if canonical_type.spelling.startswith("tag"):#     print("--", canonical_type.spelling)if canonical_type.kind == TypeKind.RECORD:struct_decl = canonical_type.get_declaration()struct_defs.append({'new_name': node.spelling,'members': list(get_struct_members(struct_decl))})def get_struct_members(struct_decl):for child in struct_decl.get_children():if child.kind == CursorKind.FIELD_DECL:# print("type:", child.type.spelling, child.type.get_array_size(), child.type.get_canonical().spelling)child_type = child.type.spellingchild_name = child.spellingif child.type.get_array_size() != -1:  # 数组需要特殊处理prefix = child_type.split('[')[0]child_type = prefixchild_name += child.type.spelling.replace(prefix, "")# print("----", child_type, child_name)if child_type == 'int' or child_type == 'int *':child_type = '没定义_自己处理'yield {'type': child_type,'name': child_name}def generate_cpp_struct(def_info):lines = [f"struct {def_info['new_name']}","{"]for member in def_info['members']:lines.append(f"    {member['type']} {member['name']};")lines.append("};\n")return '\n'.join(lines)# AST遍历for node in tu.cursor.get_children():analyze_typedef(node)# 生成纯净CPP代码output_content = """#ifndef MEMORYDEFINE_H
#define MEMORYDEFINE_H
#include "CmpStd.h"
// ST语言的数据类型所占用的字节数:https://blog.csdn.net/u013186651/article/details/135324625
// 默认string类型的字节为:80 + 1
// 转换之后,假如出现了 int ,那么这个类型应该就是没有被正确识别,需要手动替换处理// 有很多系统的第三方的库结构是没办法导出,因此需要自己在PLC系统中测量,然后自行用数组类型替换
// 替换的目的是内存对齐
// SMC_POS_REF -->48 Byte
// MC_KIN_REF_SM3 --> 8 Byte
// Kin_ArticulatedRobot_6DOF --> 760 Byte"""output_content += '\r\n'.join([generate_cpp_struct(d) for d in struct_defs])output_content += '\r\n#endif // MEMORYDEFINE_H'# 写入文件os.makedirs(os.path.dirname(output_file), exist_ok=True)with open(output_file, 'w', encoding='utf-8') as f:f.write(output_content)if __name__ == "__main__":source_code = """#include "CmpStd.h"// 系统未定义或者M4文件没有导出的类型,然后又通过PLC程序知道其长度struct SMC_POS_REF{int8_t data[48];};struct MC_KIN_REF_SM3{int8_t data[8];};struct Kin_ArticulatedRobot_6DOF{int8_t data[760];};"""# 读取m4文件内容with open(m4FilePath, 'r', encoding='utf-8') as m4File:content = m4File.read()# 找到开始和结束标记的位置start_marker = """
#ifdef __cplusplus
extern "C" {
#endif
"""end_marker = """
#ifdef __cplusplus
}
#endif
"""start_index = content.find(start_marker) + len(start_marker)end_index = content.find(end_marker)print(start_index, end_index)# 检查是否找到了开始和结束标记if start_index != -1 and end_index != -1:# 截取标记之间的内容extracted_content = content[start_index:end_index]source_code += extracted_content# 创建一个临时文件with open(middleFilePath, 'w', encoding='utf-8') as middleFile:# 将源代码字符串写入文件middleFile.write(source_code)# 确保内容被写入磁盘middleFile.flush()print("开始转换")convert_c_struct_to_cpp(middleFilePath, outputFilePath)print("操作完成-----》")else:print("m4文件内容有误,无法提取")

脚本的一些注意事项已经在代码中注释了,就不另外说明了。
运行完脚本,就可以得到了符合我们需求的c++格式的代码文件了

在这里插入图片描述脚本先将M4文件中的主要内容提取出来,然后添加一个头文件保存为一个中间文件。此时这个中间文件的结构体的定义还是c风格的。
然后将此中间文件交给clang解析,将结构体的内容分析出来,然后再将结构体的名称由原来的带tag的替换成没有带tag的。最后将所有结构体的内容保存成一个cpp风格的h文件。
需要注意的是,生成的头文件中有很多不必要的信息,自己手动删除即可。

3.添加RTTR注册信息

从我们前一篇文章可以知道,要使用RTTR的功能,必须要对每一个结构体进行注册处理。我们结构体这么多,一个个手动写代码,不现实。我们还是用脚本来自动处理吧,这个脚本输入的是前面脚本生成的头文件:

import sys
import clang.cindex
from clang.cindex import CursorKindclang.cindex.Config.set_library_path("D:/Qt/clang+llvm-20.1.0-x86_64-pc-windows-msvc/bin")srcFilePath = "./output/memorydefine.h"
dstFilePath = "./output/memorydefine.cpp"def get_struct_members(cursor):members = []for child in cursor.get_children():if child.kind == CursorKind.FIELD_DECL:member_type = child.type.spelling# 处理数组类型(保留方括号)if child.type.get_array_size() != -1:array_size = child.type.get_array_size()member_type = f"{child.type.element_type.spelling}[{array_size}]"members.append((child.spelling, member_type))return membersdef generate_rttr_code(structs_map):code = "RTTR_REGISTRATION\n{\n"for struct_name, members in structs_map.items():code += f"    registration::class_<{struct_name}>(\"{struct_name}\")\n"for member_name, _ in members:code += f"    .property(\"{member_name}\", &{struct_name}::{member_name})(policy::prop::as_reference_wrapper)\n"code += "    ;\n\n"code += "}"return codedef analyze_header(file_path):index = clang.cindex.Index.create()# Windows特定参数args = ['-std=c++11','-x', 'c++',  # 强制按C++模式解析# r'-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include',  # MSVC头文件路径# r'-IC:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt',  # Windows SDK路径,r'-IC:\Program Files\CODESYS 3.5.19.60\CODESYS\CODESYS Control SL Extension Package\4.10.0.0\ExtensionSDK\include',r'-ID:\Qt5.15\5.15.2\msvc2019_64\include\QtCore']tu = index.parse(file_path, args=args)structs = {}def visit_node(cursor):if cursor.kind == CursorKind.STRUCT_DECL and cursor.is_definition():struct_name = cursor.spellingif struct_name and not struct_name.startswith('_'):  # 忽略匿名结构体structs[struct_name] = get_struct_members(cursor)for child in cursor.get_children():visit_node(child)visit_node(tu.cursor)return generate_rttr_code(structs)if __name__ == "__main__":# print(analyze_header("MyStruct.h"))# print(analyze_header("E:\zhongyong\zyQt\Robot\CommunicationTest\communication\sharedMemory\memorydefine.h"))fileContent = """#include "memorydefine.h"#include <rttr/registration>
#include <rttr/type>
#include <vector>using namespace rttr; 
"""fileContent += analyze_header(srcFilePath)with open(dstFilePath, 'w', encoding='utf-8') as f:f.write(fileContent)

处理完之后,我们就得到了RTTR注册的代码
在这里插入图片描述这个处理后生成的代码,就保存成cpp文件。只要将前面生成的h文件一起加到我们自己的工程,我们就可以对PLC放在共享内存上的结构体全知全晓了。

4.读取PLC变量值

读取变量直接将结构体指针指向约定好的那一块共享内存,然后读即可。
可以选择直接用变量名读,也可以通过RTTR的字符串属性来读,选择你喜欢的方式就好。

5.更改PLC变量值

这个稍微复杂一些。
首先,在我们已经在【基于RTTR在C++中实现结构体数据的多层级动态读写】中实现了获取某个子成员地址相对于主数据的地址的偏移,而经过测试、CodeSys上的数据结构及结构体的对齐策略是与Qt这边是一致的。
因此,我们完全可以将要写的变量的值+类型+地址的偏移发送给PLC,PLC收到之后,按照偏移来对变量赋值。
在这里插入图片描述
要实现这个功能,得灵活使用结构体、指针和共用体。
更加具体的代码就不详述了。

6.Qt读写CodeSys的共享内存

直接用QSharedMemory来读写的话,无论在Windows下还是Linux下,都是不行的。
在Windows报权限不足,哪怕你加了Global\\也不行;
因此,在Windows下使用系统api;在Linux下,用QFile读写或者QFile::map来操作/dev/shm下的对应的文件。

#include <windows.h>---// 读共享内存{// 打开已存在的共享内存HANDLE hMapFile = OpenFileMapping(FILE_MAP_WRITE | FILE_MAP_READ,    // 读写权限FALSE,                  // 不继承句柄L"Global\\PLC_MEMORY_WRITE");if (hMapFile == NULL) {std::cerr << "OpenFileMapping fail: " << GetLastError() << std::endl;return 1;}// 映射内存视图LPVOID pBuffer = MapViewOfFile(hMapFile,FILE_MAP_ALL_ACCESS,0,0,1024);if (pBuffer == NULL) {std::cerr << "MapViewOfFile失败: " << GetLastError() << std::endl;CloseHandle(hMapFile);return 1;}qDebug() << "the read pointer:" << pBuffer;}// 写共享内存{// 打开已存在的共享内存HANDLE hMapFile = OpenFileMapping(FILE_MAP_WRITE | FILE_MAP_READ,    // 读写权限FALSE,                  // 不继承句柄L"Global\\PLC_MEMORY_READ");if (hMapFile == NULL) {std::cerr << "OpenFileMapping fail: " << GetLastError() << std::endl;return 1;}// 映射内存视图LPVOID pBuffer = MapViewOfFile(hMapFile,FILE_MAP_ALL_ACCESS,0,0,1024);if (pBuffer == NULL) {std::cerr << "MapViewOfFile失败: " << GetLastError() << std::endl;CloseHandle(hMapFile);return 1;}qDebug() << "the write pointer:" << pBuffer;}

参考:
【基于RTTR在C++中实现结构体数据的多层级动态读写】
【共享内存 - C#与CoDeSys通讯】
【clang 在 Windows 下的安装教学】
【CodeSys平台ST语言编程】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/72484.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大模型架构全景解析:从Transformer到未来计算范式

1. Transformer 架构 核心模型 GPT-4、BERT、T5、LLaMA、通义千问、文心ERNIE 关键技术 多头注意力&#xff1a;GPT-4 使用 96 头注意力位置编码创新&#xff1a;LLaMA 采用 RoPE&#xff08;旋转位置编码&#xff09;&#xff0c;Claude 3 引入 ALiBi归一化优化&#xff1…

AI第一天 自我理解笔记--微调大模型

目录 1. 确定目标&#xff1a;明确任务和数据 2. 选择预训练模型 3. 数据预处理 (1) 数据清洗与格式化 (2) 划分数据集 (3) 数据加载与批处理 4. 构建微调模型架构 (1) 加载预训练模型 (2) 修改模型尾部&#xff08;适配任务&#xff09; (3) 冻结部分层&#xff08;可…

计算机视觉——深入理解卷积神经网络与使用卷积神经网络创建图像分类算法

引言 卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称 CNNs&#xff09;是一种深度学习架构&#xff0c;专门用于处理具有网格结构的数据&#xff0c;如图像、视频等。它们在计算机视觉领域取得了巨大成功&#xff0c;成为图像分类、目标检测、图像分…

[C++面试] 关于deque

一、入门 1、deque与vector的区别 deque的迭代器包含以下信息&#xff1a; 当前缓冲区指针&#xff08;current_buffer&#xff09;当前元素在缓冲区内的位置&#xff08;current&#xff09;中控器的位置&#xff08;map&#xff09; 每次移动迭代器时&#xff0c;需检查是…

服务性能防腐体系:基于自动化压测的熔断机制

01# 背景 在系统架构的演进过程中&#xff0c;项目初始阶段都会通过压力测试构建安全护城河&#xff0c;此时的服务性能与资源水位保持着黄金比例关系。然而在业务高速发展时期&#xff0c;每个冲刺周期都被切割成以业务需求为单位的开发单元&#xff0c;压力测试逐渐从必选项…

SpringBoot 和vue前后端配合开发网页拼图10关游戏源码技术分享

今天分享一个 前后端结合 的网页游戏 开发项目源码技术。 这也是我第一次写游戏类的程序&#xff0c;虽然不是特别复杂的游戏&#xff0c;但是是第一次写&#xff0c;肯定要记录一下了&#xff0c;哈哈。 游戏的内容 就是 我们显示中玩的那个 拼图碎片的 游戏&#xff0c;类似下…

【k8s002】k8s健康检查与故障诊断

k8s健康检查与故障诊断 ‌一、集群状态检查‌ ‌检查节点健康状态‌ kubectl get nodes -o wide # 查看节点状态及基本信息 kubectl describe node <node-name> # 分析节点详细事件&#xff08;如资源不足、网络异常&#xff09; kubectl top nodes …

01-Canvas-使用fabric初始

fabric官网&#xff1a; https://fabric5.fabricjs.com/demos/ 创建画布并绘制 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sca…

【机器学习-基础知识】统计和贝叶斯推断

1. 概率论基本概念回顾 1. 概率分布 定义: 概率分布(Probability Distribution)指的是随机变量所有可能取值及其对应概率的集合。它描述了一个随机变量可能取的所有值以及每个值被取到的概率。 对于离散型随机变量,使用概率质量函数来描述。对于连续型随机变量,使用概率…

常见限流算法及实现

1. 固定窗口计数器&#xff08;Fixed Window Counter&#xff09; 原理&#xff1a;在固定时间窗口&#xff08;如1分钟&#xff09;内统计请求数&#xff0c;超过阈值则拒绝后续请求。优点&#xff1a;实现简单&#xff0c;内存占用低。缺点&#xff1a;存在窗口切换时的流量…

《TCP/IP网络编程》学习笔记 | Chapter 18:多线程服务器端的实现

《TCP/IP网络编程》学习笔记 | Chapter 18&#xff1a;多线程服务器端的实现 《TCP/IP网络编程》学习笔记 | Chapter 18&#xff1a;多线程服务器端的实现线程的概念引入线程的背景线程与进程的区别 线程创建与运行pthread_createpthread_join可在临界区内调用的函数工作&#…

创新实践分享:基于边缘智能+扣子的智能取物机器人解决方案

在 2024 年全国大学生物联网设计竞赛中&#xff0c;火山引擎作为支持企业&#xff0c;不仅参与了赛道的命题设计&#xff0c;还为参赛队伍提供了相关的硬件和软件支持。以边缘智能和扣子的联合应用为核心&#xff0c;参赛者们在这场竞赛中展现出了卓越的创新性和实用性&#xf…

QT:动态属性和对象树

动态对象 1.添加Q_PROPERTY对象 #ifndef MYPROPERTYCLASS_H #define MYPROPERTYCLASS_H#include <QObject>class MyPropertyClass : public QObject {Q_OBJECTQ_PROPERTY(QString mask READ mask WRITE setMask NOTIFY maskChanged) public:explicit MyPropertyClass(Q…

MobileNet家族:从v1到v4的架构演进与发展历程

MobileNet 是一个专为移动设备和嵌入式系统设计的轻量化卷积神经网络&#xff08;CNN&#xff09;家族&#xff0c;旨在在资源受限的环境中实现高效的图像分类、对象检测和语义分割等任务。自 2017 年首次推出以来&#xff0c;MobileNet 经历了从 v1 到 v4 的多次迭代&#xff…

在 Windows 上使用 choco 安装 mkcert 并配置 Vue 运行HTTPS

解决在Windows上使用Vue本地运行HTTPS的问题,vue-cli或vite都可以使用 步骤 1&#xff1a;确认 Chocolatey 是否已安装 1. 检查 choco 命令是否可用 打开 PowerShell&#xff08;管理员权限&#xff09;&#xff0c;输入&#xff1a; choco -v如果显示版本号&#xff08;如…

【PHP】新版本特性记录(持续更新)

文章目录 前言PHP 7.01&#xff09;NULL合并运算符&#xff1a;??2&#xff09;参数、返回值支持类型声明3&#xff09;太空船操作符&#xff1a;<>4&#xff09;通过 define 定义常量数组5&#xff09;匿名类实例化6&#xff09;字符串里使用\u转义unicode codepoint …

【记】如何理解kotlin中的委托属性?

1. 什么是委托属性&#xff1f; 委托属性的核心思想是&#xff1a; 你可以将一个属性的 getter 和 setter 的逻辑交给一个外部对象&#xff08;称为委托对象&#xff09;来处理。这个外部对象负责存储属性的值&#xff0c;并提供自定义的 get 和 set 行为。 通过委托属性&am…

使用自动导入后,eslint报错 eslint9

前提&#xff1a;使用pnpm create vuelatest创建vue应用&#xff0c;并且在创建项目时就勾选eslint和prettier&#xff0c;不然有些配置还需要手动配&#xff0c;比如解决eslint和prettier的冲突问题 1. 解决使用自动导入后Eslint报错问题 配置vite.config.ts // 自动导入api…

springboot EasyExcel 实现导入导出

1. 添加依赖 确保 Maven 依赖中包含 EasyExcel 3.0.5&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version></dependency><!-- excel工具 --><dep…

实现悬浮按钮拖动,兼容h5和微信小程序

h5用js写&#xff0c;微信小程序用 代码里面没有完全实现吸附边缘的功能&#xff0c;需要吸附边缘的话还得自己再完善下&#xff08;h5的吸附边缘是可以的&#xff0c;小程序的还有点问题&#xff09; 主要功能是&#xff1a;图片上写文字的悬浮按钮&#xff0c;文字使用的是…