pyqt漂亮gui界面模板_一种基于模板的C代码自动生成方法
在做C代碼項目的時候,我們期望做到代碼的高復用,高復用意味著代碼的高配置性,即通過簡單的配置修改達到復用代碼的目的。如果代碼高復用,支持靈活的配置,那么完全可以在上邊做一個更簡單的配置工具,用來修改代碼配置,這么做相對于提供可以配置的.c/.h源代碼有一些好處:
配置轉換為容易理解的GUI描述,配置人員不需要深入理解C代碼即可以實現配置
如果你只是想封裝一個庫給你的客戶,你可以同時提供這樣一個建議工具,即可以保護你的核心代碼,也可以讓客戶容易上手
然而,能夠實現基于模板的自動代碼的前提是,你的原始C代碼要足夠靈活,剩下需要做的就是根據用戶的輸入信息,調整某些可以修改的參數或者調用,而這些配置可以存儲在一些標準的數據存儲格式中(如,xml,json,甚至于數據庫等等)最后解析配置數據,生成配置相關的.c和.h文件。
在這一篇文章中我們將通過一個示例,來用一種簡化的方式講解這種代碼生成的概念,希望讀者能管中窺豹,獲得一些靈感,并在工作中能夠應用起來。
溫馨提示
技術性文章,可能會包含大量代碼和圖片,可以訪問原文,或者電腦訪問獲得更好的閱讀體現,另外代碼也可在GitHub上找到,具體見文中鏈接。
1
目標分析
我們將以汽車上廣泛使用到的UDS協議中常用到的0x31服務作為自動代碼的實現目標,該服務用于請求ECU執行特定的函數(服務代碼),而具體函數(服務)的內容則是由不同的OEM自己定義的,這是一個很典型的可以用自動代碼生成來處理的場景,一般來說0x31的相關的c代碼會長下邊這個樣子:
const UDS_Routine_Ctrl_T UDS_RountineControl_Services[] ={ {0xFF00, (UDS_Routine_Ctrl_Func_T)Erase_Flash_Start,(UDS_Routine_Ctrl_Func_T)NULL, (UDS_Routine_Ctrl_Func_T)NULL, 0x01}, {0xDF00, (UDS_Routine_Ctrl_Func_T)Check_CRC_Start, (UDS_Routine_Ctrl_Func_T)NULL, (UDS_Routine_Ctrl_Func_T)NULL, 0x00}, {0xFF01 , (UDS_Routine_Ctrl_Func_T)Check_Dependencies_Start,(UDS_Routine_Ctrl_Func_T)NULL, (UDS_Routine_Ctrl_Func_T)NULL, 0x01} };通常來講在協議棧核心代碼中,會查詢類似這樣表格中的元素,表格每一個元素代表一個自服務的相關配置,核心代碼會根據收到的子服務ID(類似于0xFF00,0xDF00這些)然后在這張表格中找到對應的配置,從而根據這些配置去執行響應的動作。
因此針對不同的OEM需求,協議棧針對0x31服務的核心處理算法是一致的,區別僅僅是子服務的內容,而子服務的內容配置,抽象出來就是:
ID – 子服務的ID
startFunc – 啟動函數,用來啟動服務
stopFunc – 停止函數,用來停止服務
resultFunc – 獲取結果函數
access_level – 訪問等級
然后,自動代碼生成的關鍵就是通過一個程序,能夠讀取配置數據,數據中包括上邊這些信息,然后生成必要的.c 和 .h代碼,而在這些.c或者.h中,有很多內容是不變的,例如UDS_RountineControl_Services這個數組的名稱和類型,等等,因此你可以這么想象這個過程,我們會創建一些模板,模板中含有這些不變的部分,而可變的部分則留空(類似于填空題),而留空部分則根據配置數據,動態的填入,最終填完所有的空就形成了完整的C代碼內容,將這些內容保存為.c或者.h文件就完成了代碼生成的過程。
2
趁手的工具
根據上文的分析,自動代碼生成工具有幾個組成部分:
配置數據錄入:就是人機界面,可以使用PyQt做桌面版(可以參考本站的PyQt系列教程),也可以做成網頁應用,這不是本篇文章的重點,因此,我們本篇文章的代碼不會實現這部分
配置數據存儲:json,xml,數據庫,或者更簡單一點使用變量存放在內存中(如果不需要保存到電腦上供分享或者下次使用),本文使用json來作為演示
將配置轉換為代碼:將填空題填完的過程,我們使用Python的第三方模板庫實現,有很多可選的比如Mako, Jinja等,這是本篇文章要講述的重點部分,我們將使用Jinja2模板引擎
代碼模板:留有空位的填空題,也是C代碼中不變的部分
Python中的模板引擎主要是配合web框架實現動態生成html,本質上來
說html只是給瀏覽器用來做解析的文本文件,而同樣的C源代碼本質上來說是給編譯器用來做編譯的文本文件,因此使用web模板引擎來動態生成C源代碼是沒有任何問題的。
安裝,并導入庫
pip install jinja2import jsonfrom jinja2 import Environment, FileSystemLoader, select_autoescape3
準備填空題 – 創建模板
首先我們要準備好填空題,所謂填空題就是代碼模板文件,模板中還有固定內容和可變內容,固定內容為C源代碼中不需要動態生成的部分,而可變內容則是依賴于用戶給定的配置數據來生成的部分。
針對固定內容,很簡單的,在模板文件中就是普通的符合C語言語法的文本。
針對可變內容,在模板文件中則是安裝模板引擎語法放置的占位符,這些內容將由模板引擎結合配置數據(也就是這些空位的答案)進行渲染生成。
根據我們在第一章節的分析,我們期望針對一個自服務,配置5個配置項,而用戶可以添加任意個自服務,同時針對自服務的服務函數,他們的類型都是一樣的(返回值和參數),因此我們還可以生成響應的服務函數框架。
實現一系列優雅的模板,需要掌握模板引擎的語法,Jinja的語法可以在這個鏈接中找到:https://jinja.palletsprojects.com/en/2.11.x/templates/
我們來創建一個名為srv31_pbconfig.ct,文件的后綴并不是很重要,這里選取了ct,含義似c-template,然后添加我們的模板代碼
#include "uds.h"{% for item in services %}{% if item.start_fun != "NULL" %}static uint8_t {{ item.start_fun }}(void);{% endif %}{% if item.stop_fun != "NULL" %}static uint8_t {{ item.stop_fun }}(void);{% endif %}{% if item.result_fun != "NULL" %}static uint8_t {{ item.result_fun }}(void);{% endif %}{% endfor %}const UDS_Routine_Ctrl_T UDS_RountineControl_Services[] ={ {% for item in services %} { {{ item.id }}, (UDS_Routine_Ctrl_Func_T){{ item.start_fun }}, (UDS_Routine_Ctrl_Func_T){{ item.stop_fun }}, (UDS_Routine_Ctrl_Func_T){{ item.result_fun }}, {{ item.access_level }}}, {% endfor %}};/*! * The size of the rountine control table, it will be updated automatically, no need to change this. */const uint16_t UDS_RountineControl_Services_Size = sizeof(UDS_RountineControl_Services)/sizeof(UDS_Routine_Ctrl_T);{% for item in services %}{% if item.start_fun != "NULL" %}static uint8_t {{ item.start_fun }}(void){ uint8_t status; /*!!!!! User need to complete this function */ return status;}{% endif %}{% if item.stop_fun != "NULL" %}static uint8_t {{ item.stop_fun }}(void){ uint8_t status; /*!!!!! User need to complete this function */ return status;}{% endif %}{% if item.result_fun != "NULL" %}static uint8_t {{ item.result_fun }}(void){ uint8_t status; /*!!!!! User need to complete this function */ return status;}{% endif %}{% endfor %}其中:使用{% %}括起來的代碼就是Jinja2的模板語法,也就是我們一直提到的可變內容,引擎會在渲染的時候解析這些語法,動態的填入內容。
模板中的3到13行,用于生成函數申明
模板中的17到19行,用于生成子服務數組
模板中的27到57行,用于生成函數體定義
模板中的其他部分為固定內容
模板語法是跟python語法基本一致,模板中僅僅用到了一個數據結構,那就是services變量,它是一個python list,里邊包含了所有子服務數據。將會由配置數據提供。
4
準備答案 – 創建配置數據
接下來為了能夠正確的將填空題的空白填滿,我們要提供正確的數據,我們將使用json文件存儲這些數據,在本文中,我們將手工編寫json文件,而在實際應用中,這個文件通常通過GUI的方式,由用戶通過界面錄入。
{ "service_31": [ { "id": "0xFF00", "start_fun": "Erase_Flash_Start", "stop_fun": "NULL", "result_fun": "NULL", "access_level": "0x01" }, { "id": "0xDF00", "start_fun": "Check_CRC_Start", "stop_fun": "NULL", "result_fun": "Get_CRC_Result", "access_level": "0x00" }, { "id": "0xFF01", "start_fun": "Check_Dependencies_Start", "stop_fun": "Check_Dependencies_Stop", "result_fun": "NULL", "access_level": "0x01" } ]}在這個配置數據文件中,我們提供了3個0x31服務的配置,并指定了對應的ID,start,stop,result函數,以及access_level。
5
完成填空,交卷 – 渲染
最后,我們將數據和模板組合在一起進行渲染,將答案填到填空題的空白中。
env = Environment( loader=FileSystemLoader('.'), trim_blocks=True ) srv31_template = env.get_template('srv31_pbconfig.ct') with open('auto_coding_config.json', 'r') as f: config=json.load(f) srv31_source_code = srv31_template.render(services=config['service_31']) with open('srv31_pbconfig.c', 'w') as f: f.write(srv31_source_code)首先我們創建了Jinja環境,并指定環境的loader是一個以當前目錄創建的FileSystemLoader,也就是說環境會在當前目錄中尋找模板文件,而我們創建的模板文件名為srv31_pbconfig.ct,因此在下邊一行代碼,可以使用env的get_template方法將名為srv31_pbconfig.ct的模板加載進來,如果你有多個模板,因為前邊已經通過loader告知環境,這個目錄下存放所有模板,所以通過模板文件名就可以取回模板,并創建模板對象。
然后我們打開數據配置文件,將配置讀入config變量。
第三步,我們使用模板對象的render方法,將配置數據渲染進去,也就是我們比喻為填答案的過程,渲染的結果將通過這個方法返回
最后,將渲染結果保存到目標文件中,就是我們的自動生成的C代碼。因為代碼略長,我們就不貼在這篇文章里邊了,如果需要,大家可以參考github倉庫:
https://github.com/pythonlibrary/auto-coding-demo
6
總結
本文以最簡單的例子,介紹了一種使用模板引擎來自動生成C代碼的方法,雖然例子很簡單,但是方法可以擴展到非常復雜的應用,在做類似應用的過程中,其實是一個Python代碼(或者代碼生成器)和C原始代碼(模板)的一個平衡,哪一部分放在哪個代碼里邊實現是需要仔細斟酌考量的,希望這篇文章能夠給你一些靈感。
7
小抄 – 參考文檔
Jinja官方文檔:
https://jinja.palletsprojects.com/
END
長按掃碼關注
或用電腦訪問網頁以獲取更好的閱讀體驗:
https://pythonlibrary.net/
總結
以上是生活随笔為你收集整理的pyqt漂亮gui界面模板_一种基于模板的C代码自动生成方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网页按钮跳转位置_阻止safari从网页
- 下一篇: ug二次开发菜单中文乱码_平面用cad,