注:本文首发于“嵌入式软件实战派”公众号,关注该公众号可获得更多精品干货。
▍很懒很操心
有一次,我在项目开发中想监控某段空间数据的大小,即这段空间在MCU中非常有限,希望每个版本在集成软件的时候都想获取其使用了多少空间,防止某些愣头青不珍惜内存,乱塞东西。而这段空间,我定义了一个神一样的结构体映射到这个空间,即其他开发人员只要在结构体增加元素即可(我使用洪荒之力将宏定义发挥到淋漓尽致才做到的,至于怎么实现的细节就不在这个文章讨论了,后续再写篇文章装装X)。
计算这个结构体空间,要求:
-
在软件集成阶段就获得这个结构体大小,而不是MCU运行的时候;(自动执行)
-
计算这个结构体大小,不要增加删除原程序代码;(悄无声息)
-
方便集成工程师使用,不增加使用难度;(简单易用)
-
不因为人操作的原因,而导致计算结果不准确,即自动化执行输出结果。(无人为干扰)
总之,做这件事的目的是:每次集成的时候自动输出结果(很懒),也不行交给其他小伙伴去手工计算,或者更改原来的结构代码去计算这个空间,怕其乱来搞坏了原来的代码(很操心)。
再总之:能让电脑干的活,干嘛要让人去干!
于是,我就突发奇想,写个脚本呗。
那么啥子脚本可以计算C语言的结构体大小?
身为优秀的“嵌入式”工程师,对这种将C语言“嵌入”到脚本中的事情肯定是要研究一番的。
Note:为了方便描述,我将具体项目细节和装X的过程隐去,并将这个神一样的结构体简化为:
typedef struct
{
unsigned char item_a[2];
unsigned char item_b[3];
unsigned char item_c[5];
unsigned char item_d[8];
unsigned char item_e[33];
unsigned char item_fxxk[1];
unsigned char item_fxxxk[2];
unsigned char item_fxxk_any[55];
// add items here...
}typeStructData;
▍将C/C++代码嵌入Python
人生苦短,我用Python
温馨提示,使用以下方法,请提前安装:
-
Python
-
GCC(例如Windows上的MinGW)
-
用pip安装pyembedc(
pip install pyembedc
)
注意:Python的版本位数要跟GCC的对应,例如都选32位的。
方法1:Python调用exe方式
步骤:
-
在一个临时C文件里,编写临时
main
函数; -
用GCC构建编译,生成exe;
-
通过脚本(此处选择Python)调用运行输出结果;
-
删除临时C文件和exe文件。
接上代码看看
// struct.h
typedef struct
{
unsigned char item_a[2];
unsigned char item_b[3];
unsigned char item_c[5];
unsigned char item_d[8];
unsigned char item_e[33];
unsigned char item_fxxk[1];
unsigned char item_fxxxk[2];
unsigned char item_fxxk_any[55];
}typeStructData;
import os
c_main = r'''
#include <stdio.h>
#include "struct.h"
int main(void)
{
printf("size: %d\n", sizeof(typeStructData));
return 0;
}
'''
def cal_struct_size():
f_c_main = 'xxxxsizeofstructxxxx.c'
f_run = 'xxxxsizeofstructxxxx.exe'
with open(f_c_main, 'w') as f: f.write(c_main)
gcc_compile = "gcc %s -o %s"%(f_c_main, f_run)
os.system(gcc_compile)
os.system(f_run)
if os.path.exists(f_c_main): os.remove(f_c_main)
if os.path.exists(f_run): os.remove(f_run)
if __name__ == "__main__":
cal_struct_size()
方法2:Python调用lib方式
总觉得用Python调用exe的方式有点low,再进一步,那就调用lib吧,例如调用.so
内的函数或者变量。
步骤跟方法1类似:
-
在一个临时C文件里,编写临时
main
函数; -
用GCC构建编译,生成lib(
.so
); -
通过Python调用运行输出结果;
-
删除临时C文件。
Python调用lib库有个好处,可以调用其里面的具体函数等。
// struct.c
#include <stdio.h>
typedef struct
{
unsigned char item_a[2];
unsigned char item_b[3];
unsigned char item_c[5];
unsigned char item_d[8];
unsigned char item_e[33];
unsigned char item_fxxk[1];
unsigned char item_fxxxk[2];
unsigned char item_fxxk_any[55];
}typeStructData;
int get_struct_size(void)
{
return sizeof(typeStructData);
}
import ctypes
import os
os.system('gcc -shared -Wl,-soname,struct -o struct.so -fPIC struct.c')
struct_size = ctypes.cdll.LoadLibrary('./struct.so')
def cal_struct_size():
s = struct_size.get_struct_size()
print("size: %d"%s)
if __name__ == "__main__":
cal_struct_size()
# if os.path.exists('struct.so'): os.remove('struct.so')
貌似有个小问题,如果想在脚本里面删除这个.so文件,会出现问题,因为没有办法unload这个.so
。另外,关于这个话题,请参考:https://stackoverflow.com/questions/359498/how-can-i-unload-a-dll-using-ctypes-in-python
方法3:Python调用C源码方式
调用exe和调用lib,都觉得很low,怎么办,能不能直接插入C源码呢?
那就用pyembedc吧,Python可以访问C的变量,C也可以访问Python的变量,是不是炫酷吊炸天。
例1,访问C内部变量
# callstruct_inline1.py
from pyembedc import C
struct_str = r'''
typedef struct
{
unsigned char item_a[2];
unsigned char item_b[3];
unsigned char item_c[5];
unsigned char item_d[8];
unsigned char item_e[33];
unsigned char item_fxxk[1];
unsigned char item_fxxxk[2];
unsigned char item_fxxk_any[55];
}typeStructData;
struct_size = sizeof(typeStructData);
'''
struct_size = 0
struct_f = C(struct_str)
print('size: %d\n'%struct_size)
例2,访问C内部函数
# callstruct_inline2.py
from pyembedc import embed_c
struct_str2 = r'''
typedef struct
{
unsigned char item_a[2];
unsigned char item_b[3];
unsigned char item_c[5];
unsigned char item_d[8];
unsigned char item_e[33];
unsigned char item_fxxk[1];
unsigned char item_fxxxk[2];
unsigned char item_fxxk_any[55];
}typeStructData;
int get_struct_size(void)
{
return sizeof(typeStructData);
}
'''
struct_c = embed_c(struct_str2)
print('size: %d\n'%struct_c.get_struct_size())
实际上,以上的操作,也是这个库偷偷地调用了GCC来编译C代码的(只是不是显式让你看到而已),你不安装对应版本的GCC也是做不到的。
顺便说是,这个pyembedc有几个方式:
pyembedc.C(string) -> int
pyembedc.inline_c(string) -> int
pyembedc.inline_c_precompile(string) -> int
These functions will compile
string
containing the C/C++ code or directives (see below) and then link dynamically and run the code.
string
is C/C++ code that can be used within a function. It can contain any valid C/C++ expression that your compiler will support.The
C
function will automatically provide references to all local Python variables for use in your code to read or write as if they were basic types or arrays.The
inline_c
andinline_c_precompile
fucntion will not provide references to local Python variables and thus is faster and consumes less memory.
pyembedc.embed_c(string) -> cdll
pyembedc.embed_c_precompile(string) -> cdll
These functions are used to compile code but not execute immediately. They return a CDLL object (see the CDLL python module) that can be executed later.
更多内容,请见:https://github.com/ftrias/pyembedc
▍将C/C++代码嵌入Ruby
生活诗意,我用Ruby
能把C代码塞进Python,当然也能塞进Ruby。对于在Ruby上插入C源码,给大家安利一个库RubyInline。
Inline允许您在Ruby代码中编写外部代码。它会自动确定相关代码是否已更改,并仅在必要时进行构建。然后将扩展自动加载到定义扩展的类/模块中。您甚至可以编写额外的构建器,使您可以用任何语言编写Inline代码。使用
Inline :: C
作为Module,并在Module#inline
中查找所需的API。
RubyInline还有以下Features:
-
快速,轻松地内嵌在ruby脚本中的C或C ++代码。
-
可扩展以与其他语言一起使用。
-
Ruby和C基本类型之间的自动转换
* char,unsigned,unsigned int,char *,int,long,unsigned long
-
inline_c_raw用于自动转换不充分时。
-
仅当内联代码已更改时才重新编译。
-
假装是安全的。
-
仅需要标准的ruby库,无需下载其他内容。
require "inline"
class MyTest
inline do |builder|
builder.c "
long factorial(int max) {
int i=max, result=1;
while (i >= 2) { result *= i--; }
return result;
}"
end
end
t = MyTest.new()
factorial_5 = t.factorial(5)
require 'inline'
class MyTest
inline(:C) do |builder|
builder.include '<iostream>'
builder.add_compile_flags '-x c++', '-lstdc++'
builder.c '
void hello(int i) {
while (i-- > 0) {
std::cout << "hello" << std::endl;
}
}'
end
end
t = MyTest.new()
t.hello(3)
是不是很好玩,是不是很想试试?
但是我告诉你,我在Windows上没搞成功,但在Linux上搞起来了。
应了网上某句话:想学Ruby,就用Linux吧,别在Windows上瞎折腾。
话说回来,这个嵌入式C源码的用法,个人感觉Ruby的比Python的简洁直观。
该用哪种方法,看实际需要吧。
更多内容,详见:https://github.com/seattlerb/rubyinline
▍总结
想将C/C++塞进脚本,需要借助GCC,它才是让你装逼让你飞的前提条件。