我硬生生地把C代碼塞進了Python和Ruby!
▍很懶很操心
有一次,我在項目開發(fā)中想監(jiān)控某段空間數(shù)據的大小,即這段空間在MCU中非常有限,希望每個版本在集成軟件的時候都想獲取其使用了多少空間,防止某些愣頭青不珍惜內存,亂塞東西。而這段空間,我定義了一個神一樣的結構體映射到這個空間,即其他開發(fā)人員只要在結構體增加元素即可(我使用洪荒之力將宏定義發(fā)揮到淋漓盡致才做到的,至于怎么實現(xiàn)的細節(jié)就不在這個文章討論了,后續(xù)再寫篇文章裝裝X)。
計算這個結構體空間,要求:
在軟件集成階段就獲得這個結構體大小,而不是MCU運行的時候;(自動執(zhí)行)
計算這個結構體大小,不要增加刪除原程序代碼;(悄無聲息)
方便集成工程師使用,不增加使用難度;(簡單易用)
不因為人操作的原因,而導致計算結果不準確,即自動化執(zhí)行輸出結果。(無人為干擾)
總之,做這件事的目的是:每次集成的時候自動輸出結果(很懶),也不行交給其他小伙伴去手工計算,或者更改原來的結構代碼去計算這個空間,怕其亂來搞壞了原來的代碼(很操心)。
再總之:能讓電腦干的活,干嘛要讓人去干!
于是,我就突發(fā)奇想,寫個腳本唄。
那么啥子腳本可以計算C語言的結構體大???
身為優(yōu)秀的“嵌入式”工程師,對這種將C語言“嵌入”到腳本中的事情肯定是要研究一番的。
Note:為了方便描述,我將具體項目細節(jié)和裝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
溫馨提示,使用以下方法,請?zhí)崆鞍惭b:
-
Python -
GCC(例如Windows上的MinGW) -
用pip安裝pyembedc( pip install pyembedc
)
注意:Python的版本位數(shù)要跟GCC的對應,例如都選32位的。
方法1:Python調用exe方式
步驟:
在一個臨時C文件里,編寫臨時
main
函數(shù);用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
內的函數(shù)或者變量。
步驟跟方法1類似:
在一個臨時C文件里,編寫臨時
main
函數(shù);用GCC構建編譯,生成lib(
.so
);通過Python調用運行輸出結果;
刪除臨時C文件。
Python調用lib庫有個好處,可以調用其里面的具體函數(shù)等。
// 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文件,會出現(xiàn)問題,因為沒有辦法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內部函數(shù)
# 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有幾個方式:
Functionspyembedc.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 ++代碼。
可擴展以與其他語言一起使用。
紅寶石和C基本類型之間的自動轉換
* char,unsigned,unsigned int,char *,int,long,unsigned long
inline_c_raw用于自動轉換不充分時。
僅當內聯(lián)代碼已更改時才重新編譯。
假裝是安全的。
僅需要標準的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,它才是讓你裝逼讓你飛的前提條件。
本文授權轉載自公眾號“嵌入式軟件實戰(zhàn)派”,作者實戰(zhàn)派師姐
-END-
推薦閱讀
免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!