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.pswp.cn/web/72484.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/72484.shtml
英文地址,請注明出處:http://en.pswp.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;文字使用的是…