Python和Ruby嵌入C代码的N种方法

注:本文首发于“嵌入式软件实战派”公众号,关注该公众号可获得更多精品干货。

▍很懒很操心

有一次,我在项目开发中想监控某段空间数据的大小,即这段空间在MCU中非常有限,希望每个版本在集成软件的时候都想获取其使用了多少空间,防止某些愣头青不珍惜内存,乱塞东西。而这段空间,我定义了一个神一样的结构体映射到这个空间,即其他开发人员只要在结构体增加元素即可(我使用洪荒之力将宏定义发挥到淋漓尽致才做到的,至于怎么实现的细节就不在这个文章讨论了,后续再写篇文章装装X)。

计算这个结构体空间,要求:

  1. 在软件集成阶段就获得这个结构体大小,而不是MCU运行的时候;(自动执行)

  2. 计算这个结构体大小,不要增加删除原程序代码;(悄无声息)

  3. 方便集成工程师使用,不增加使用难度;(简单易用)

  4. 不因为人操作的原因,而导致计算结果不准确,即自动化执行输出结果。(无人为干扰)

总之,做这件事的目的是:每次集成的时候自动输出结果(很懒),也不行交给其他小伙伴去手工计算,或者更改原来的结构代码去计算这个空间,怕其乱来搞坏了原来的代码(很操心)。

再总之:能让电脑干的活,干嘛要让人去干!

于是,我就突发奇想,写个脚本呗。

那么啥子脚本可以计算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

温馨提示,使用以下方法,请提前安装:

  1. Python

  2. GCC(例如Windows上的MinGW)

  3. 用pip安装pyembedc(pip install pyembedc

注意:Python的版本位数要跟GCC的对应,例如都选32位的。

方法1:Python调用exe方式

步骤:

  1. 在一个临时C文件里,编写临时main函数;

  2. 用GCC构建编译,生成exe;

  3. 通过脚本(此处选择Python)调用运行输出结果;

  4. 删除临时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 
 #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类似:

  1. 在一个临时C文件里,编写临时main函数;

  2. 用GCC构建编译,生成lib(.so);

  3. 通过Python调用运行输出结果;

  4. 删除临时C文件。

Python调用lib库有个好处,可以调用其里面的具体函数等。

 // struct.c
 
 #include 
 
 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 and inline_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 ''
         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,它才是让你装逼让你飞的前提条件。

Python和Ruby嵌入C代码的N种方法_第1张图片

 

你可能感兴趣的:(C语言,脚本)