在做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 jinja2
import  jsonfrom jinja2 import Environment, FileSystemLoader, select_autoescape

3

准备填空题 – 创建模板

首先我们要准备好填空题,所谓填空题就是代码模板文件,模板中还有固定内容和可变内容,固定内容为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代码自动生成方法相关推荐

  1. matlab自动生成报告,一种基于MATLAB的Word报告自动生成方法

    总第 182期 一 种基于MATLAB的Word报告自动生成方法 孙 剑 (信阳农林学院,河南 信阳 464000) 摘要:自动生成Word文档报告功能是办公 自动化系统中的重要组成部分.为高效的完成 ...

  2. aes子密钥生成c语言_一种基于流密码算法的子密钥生成方法与流程

    本发明涉及一种用于分组加解密算法的子密钥的生成方法. 背景技术: 随着信息技术的发展,信息安全性的问题却愈来愈显得突出,保证信息安全的一个重要技术就是密码学.密码学在信息安全技术中扮演着基础的角色,是 ...

  3. lin通信ldf文件解析_基于LIN协议的代码自动生成系统及方法_2014108531085_说明书_专利查询_专利网_钻瓜专利网...

    技术领域 本发明属于汽车电子LIN网络通讯领域,公开了一种利用LDF文件自动生成LIN通讯软件代码的方法. 背景技术 目前汽车电子产品的软件开发逐渐向模块化.标准化.集成化.自动化发展.LIN通讯模块 ...

  4. html5填空题阅卷,一种基于图像识别的填空题自动阅卷方法与流程

    本发明涉及自动阅卷技术领域,尤其涉及一种基于图像识别的填空题自动阅卷方法. 背景技术: 随着电子信息的发达,越来越多的工作被计算机取代,例如,自动阅卷已经逐渐取代人工阅卷. 现有的自动阅卷系统,更多的 ...

  5. 企业微信推送消息延迟_一种基于企业微信的消息推送方法与流程

    本发明涉及消息推送技术领域,特别涉及一种基于企业微信的消息推送方法. 背景技术: 随着微信公众号的普及,微信企业号也越来越受到人们的关注.而腾讯公司在微信企业号的基础上又进行了进一步的升级,提供了类似 ...

  6. python小波分析法检测火焰_一种基于小波分析的网络流量异常检测方法

    一种基于小波分析的网络流量异常检测方法 杜臻 ; 马立鹏 ; 孙国梓 [期刊名称] <计算机科学> [年 ( 卷 ), 期] 2019(046)008 [摘要] 对大量网络流量数据进行高质 ...

  7. mysql查询耗时_一种数据库高耗时查询的自动取消方法与流程

    本发明涉及数据库的查询方法,特别涉及一种数据库高耗时查询的自动取消方法. 背景技术: 有很多关系型数据库查询业务非常耗时,比如查询企业实时报表之类的,一次查询可能需要几分钟甚至更长.在很多时候,前端业 ...

  8. 电容屏物体识别_一种基于触摸屏触摸点的物体识别方法与流程

    本发明涉及触摸屏触摸点物体识别技术领域,具体为一种基于触摸屏触摸点的物体识别方法. 背景技术: 多触点触摸屏支持多个触点同时输入,通过触摸屏的点的特征,进行物体识别是一个成熟的技术,以下简称物体识别为 ...

  9. 人脸扫描建模_一种基于三维扫描数据的人脸建模方法

    一种基于三维扫描数据的人脸建模方法 黄炎辉 1 , 樊养余 1 , 董卫军 2 [摘 要] 三维扫描仪可以准确获取人脸的几何形状与纹理,但原始的人脸扫描 数据仅为一张连续曲面,不符合实际的人脸结构,无 ...

最新文章

  1. python文件输出-Python 文件和输入输出小结
  2. linux下php的安装路径,Linux下Apache、PHP、MySQL默认安装路径
  3. 处理字符串_3_处理含引号的字符串
  4. Qt:Windows编程—DLL注入与卸载
  5. Centos7上openVP的另一种使用方式,实现访问控制!
  6. SpringBoot核心原理:自动配置、事件驱动、Condition
  7. 操作系统的不确定性是指程序执行结果的不确定性_用不确定性促销策略提高用户购买意愿...
  8. 单体内置对象_第五章 单体内置对象
  9. android 银行卡号 4位,Android中控制银行卡号的输入 即4个数字空一格
  10. 内网win10安装flash插件 修订版2021-07-05
  11. Alpha阶段测试报告
  12. 萨塞克斯大学计算机专业,萨塞克斯大学高级计算机科学专业
  13. 比较motif和一条长序列的相似性
  14. 2021年2月8日 抖音直播后端开发实习面经
  15. 不会用matplotlib画多子图?收好这2个套路
  16. Unity中利用材质自发光实现物体闪烁效果
  17. Linux系统学习了解计算机
  18. 未开启横屏时,设置了宽高为窗口100%的视频,竖屏方向播放时,变形问题解决
  19. 微信小程序自定义导航栏与自带下拉刷新冲突
  20. linux 垃圾箱位置,如何将Linux rm命令删除的文件放入垃圾箱

热门文章

  1. Vue绑定数据v-bind缩写:字段名 双向绑定v-model缩写:model 监听动作v-on缩写@ 记住:与数据相关用冒号 与动作相关用@
  2. Linux之du df free:du文件大小 df分区使用 free内存
  3. Wordpress插件
  4. linux中vi大括号enter缩进,格式 – 如何在vi中对齐代码(大括号,括号等)?
  5. 谁都忍不了烂代码,如何用重构的方式让它整洁起来?
  6. 热成像成像不清楚是什么时候_「从零搞机」热成像仪查看 分形工艺Node 202 机箱 风道散热情况...
  7. java宠物小精灵,简单的Java口袋妖怪扑灭模拟器
  8. java装饰模式模拟流_Java 装饰模式 io流
  9. java创建具体时间点_java单例饿汉模式对象创建时间点疑问
  10. java 网络编程 聊天_Java——网络编程(实现基于命令行的多人聊天室)