Linux c 与 python的互操作

1.最简单的调用方式:

int alc_FFT_of(const char *topic_fmt, int ch, cJSON *result)
{
    //Py_Initialize();

    pthread_mutex_lock(&mqtt_mutex);
    // 调用 Python 函数
    PyRun_SimpleString("print('Hello, World!')");
    pthread_mutex_unlock(&mqtt_mutex);
    
    //Py_Finalize();
    return 0;
}

注意,上面的是最简单的单向执行代码。代码如果涉及多线程执行,可以使用全局mutex来进行同步。

2.数据传递

2.1 方法一,文本模式,通过字典对象:

//在Python解释器中获取脚本的主模块和全局字典:
PyObject *pModule = NULL;
PyObject *pDict = NULL;

pModule = PyImport_AddModule("__main__");
pDict = PyModule_GetDict(pModule);
//使用PyRun_String函数来执行Python代码,并传递参数给脚本。可以使用PyEval_GetGlobals()函数获取脚本执行的全局字典,在这个字典中你可以定义脚本中可用的变量或者函数。
PyObject *pResult = NULL;
const char *script = "print('Hello, ' + name)";

PyObject *pGlobals = PyEval_GetGlobals();
PyDict_SetItemString(pGlobals, "name", PyUnicode_FromString("John"));

pResult = PyRun_String(script, Py_file_input, pDict, pGlobals);
//在上述例子中,我们将name变量传递给Python脚本。你可以根据需要传递更多的参数,只需在全局字典中设置相应的变量即可。

//在获取结果之后,你可以根据需要处理脚本的输出或返回值(如果有的话):
if (pResult != NULL) {
    if (PyErr_Occurred()) {
        PyErr_Print();
    }
    Py_DECREF(pResult);
}
//最后,不要忘记在完成后清理和关闭Python解释器:
Py_Finalize();

 2.2 方法二,涉及大量二进制数据的传递:

Python的C API提供了将二进制数据传递给Python脚本的方式。你可以使用PyBytes_FromStringAndSize函数将C/C++中的二进制数据转换为Python的bytes对象,并将其传递给脚本。

事实上,这个参数最终仍然是通过Dictionary过去的

//2进制模式传递参数的python-c互操作示例:

#include <Python.h>

int main(int argc, char *argv[]) {
    Py_Initialize();
    
    // 创建二进制数据
    unsigned char binaryData[] = {0x01, 0x02, 0x03, 0x04, 0x05};
    size_t binarySize = sizeof(binaryData);
    
    // 将二进制数据转换为Python的bytes对象
    PyObject *pBytes = PyBytes_FromStringAndSize((const char *)binaryData, binarySize);
    
    // 将bytes对象传递给脚本
    PyObject *pModule = PyImport_AddModule("__main__");
    PyObject *pDict = PyModule_GetDict(pModule);
    PyDict_SetItemString(pDict, "binary_data", pBytes);
    
    // 执行脚本
    const char *script = "print(binary_data)";
    PyRun_String(script, Py_file_input, pDict, pDict);
    
    // 释放Python对象
    Py_XDECREF(pBytes);
    
    Py_Finalize();
    return 0;
}

3. 涉及data in => python => data out的情形,建议的调用模式

3.1  C一侧的代码示例

#define PY_RUN_STANDALONE
int all_python_funcXXXXX_of(cJSON *src, cJSON *FFT, const char *py_sh_filename)
{
    if((src == NULL) ||(FFT==NULL)) return -1;
#ifdef PY_RUN_STANDALONE
    Py_Initialize();
    pModule = PyImport_AddModule("__main__");
    pDict = PyModule_GetDict(pModule);    
#endif
    // 调用 Python 函数
    const char *cstr = cJSON_Print(src);
    PyObject *pObject = PyUnicode_FromString(cstr);
    PyDict_SetItemString(pDict, "paramIn", pObject); //参数,传入paramIn字符串,直接在python可见
     // 调用 Python 函数
    FILE *file = fopen(py_sh_filename, "r");
    if(file==NULL)
    {
         //deal error....
         cJSON_AddItemToObject(FFT, "binData", cJSON_CreateString(""));
    }
    else
    {
        PyRun_File(file, py_sh_filename, Py_file_input, pDict, pDict);
        PyObject *pKeys = PyDict_GetItemString(pDict, "resultOut"); //python的传出参数,也是字符串模式
        if(pKeys == NULL)  
        {
            cJSON_AddItemToObject(FFT, "binData", cJSON_CreateString(""));
        }
        else
        {
            char *keyStr = PyUnicode_AsUTF8(pKeys); //无需手工析构
            cJSON *result = cJSON_Parse(keyStr);

            cJSON *elem = result->child;
            while (elem != NULL) {
                printf("result of python:%s\n", elem->string); fflush(stdout);
                cJSON_AddItemToObject(FFT, elem->string, cJSON_Duplicate(elem, 1));
                elem = elem->next;
            }
            cJSON_Delete(result);
        }
        //PyDict_Clear(pDict);    
        close(file);
    }
    free(cstr);
#ifdef PY_RUN_STANDALONE
    Py_Finalize();
#endif
    
    return 0;
}

3.2  Python脚本一侧的框架示例

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# paramsIn => resultOut json => json.
import json
import binascii
import numpy as np;
import sys

#print("python version: %s", sys.version);

#c input params = >paramIn
jsonStr = paramIn;
#jsonStr = '{"binData": "48656c6c6f20576f726c6421"}'

#to json obj
data = json.loads(jsonStr)

###########################################################################
# Stage 1: here input json has been transfered to variable: data

# get hex buff. got an item form json.
hex_data = data['binData']

# get byte array. 
byte_data = binascii.unhexlify(hex_data)
ba =  bytearray(byte_data);

#计算FFT
signal = ba
fft_result = np.fft.fft(signal)

# 从 FFT 结果中提取幅度谱
amplitude_spectrum = np.abs(fft_result)

# 将幅度谱长度的一半舍去
half_length = len(amplitude_spectrum) // 2
amplitude_spectrum = amplitude_spectrum[:half_length]

# to hex again.
# 将浮点数组转换为 4 字节浮点型编码的二进制数据
ba = amplitude_spectrum.astype(np.float32).tobytes()

hex_data = binascii.hexlify(ba).decode('utf-8')

# create json obj again.
data = {'binData': hex_data}

root = {
    'binData':hex_data,
    'fft_result_pt':len(signal),
    'fft_result_len': len(hex_data)
}

# Stage 2: here out json object has been established to object: root
###########################################################################

# json => final json_str_out
resultOut = json.dumps(root)

4. 循环调用、垃圾回收、异常捕获

如果仅仅是单次运行Python,上面的描述足够用。如果你需要周期性地调用Python代码。那么会有一些额外的问题需要考量,比如这个:

https://discuss.python.org/t/is-there-any-helpful-debug-tools-to-accelerate-c-python-debug/34449

因为不同的.py共用一个执行环境,它会导致名字污染,在你无意识地重定义了一个built_in  function 然后,很奇怪的事情就会出现。

4.1循环调用过程中的主动垃圾回收

//step 1.对最重要的两个外层交互用的Python对象添加引用计数
    Py_Initialize();
    pModule = PyImport_AddModule("__main__");
    pDict = PyModule_GetDict(pModule);    
    Py_INCREF(pModule);
    Py_INCREF(pDict);

//Step 2.退出时的反向操作
    Py_DECREF(pModule);
    Py_DECREF(pDict);
    Py_DECREF(pDict);    // 释放pDict对象的引用
    //ResetPyEnvironment();
    Py_DECREF(pModule);  // 释放pModule对象的引用
    Py_Finalize();

//Step3.主动在py脚本文件执行完毕后,调用py格式的垃圾回收脚本。
onst char *pyFileResetEnvironment = "./py/garbdge_collect.py";
void ResetPyEnvironment(void)
{
    InvokePythonFile(pyFileResetEnvironment);
}

4.2相关的垃圾主动回收.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import gc

#输出参数删除
resultOut = None
del resultOut
#输入参数删除
paramIn = None
del paramIn
gc.collect()

4.3 调试信息打印

        void *ret = PyRun_File(file, py_sh_filename, Py_file_input, pDict, pDict);
        if(ret ==NULL) 
        {
            //when exception occur, can print line number of bug related line.
            PyErr_Print(); 
            printf(".py invoke error %s\n", py_sh_filename);fflush(stdout);
            fclose(file);
            exit(0);
        }

附录A C与Python互操作需要的软件包

# python
yum install python3-devel

#互操作可能涉及到的cJSON
#需要下载cJSON-master源码:
cd /workerspace/sensor/src/data_dispatch/3rd/cJSON-master
make clean
make
make install
sudo ln -s /usr/local/lib/libcjson.so.1 /usr/lib/libcjson.so.1
sudo ldconfig 

#gcc
yum install gcc-c++ cmake openssl-devel -y

#也许你会用到mqtt
#mosquitto这个也要下载源码包,包含服务器,但是可以不运行。
make clean
make
make install
sudo ln -s /usr/local/lib/libmosquitto.so.1 /usr/lib/libmosquitto.so.1
sudo ldconfig 

#uuid.
yum install uuid-devel
yum install libuuid-devel

附录B 操控某个特定的python执行环境添加pip包的语法

#注意 -i --trusted-host的相对顺序,国外的服务器延迟太大,几乎无法访问
python3 -m pip install --timeout 200 --trusted-host mirrors.aliyun.com -i http://mirrors.aliyun.com/pypi/simple/ numpy

附录C  待执行的.python脚本的示例

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import binascii
import numpy
import sys

print("python version: %s", sys.version);

#c input params = >paramIn
#jsonStr = paramIn;
jsonStr = '{"xxData": "48656c6c6f20576f726c6421"}'

#to json obj
data = json.loads(jsonStr)

# get hex buff.
hex_data = data['binData']

# get byte array.
byte_data = binascii.unhexlify(hex_data)
ba =  bytearray(byte_data);

# print byte array.
print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
print(byte_data)
ba[0] = 0xff;
ba[1] = 0xff;

# to hex again.
hex_data = binascii.hexlify(ba).decode('utf-8')

# create json obj again.
data = {'binData': hex_data}

# json => str
json_string = json.dumps(data)
fft_result = json_string;
fft_result_len = len(json_string);

# print final output str.
print(json_string)
print("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")

附录D 调用.py文件的 c函数示例

    //python的传入参数部分已经介绍过,不再给出
    //...

    // 调用 Python 函数
    FILE *file = fopen(pyFileFFT, "r");
    if(file!=NULL)
    {
        PyRun_File(file, pyFileFFT, Py_file_input, pDict, pDict);

        //传出参数解析
        PyObject *ro = PyDict_GetItemString(pDict, "fft_result");
        unsigned char *buf = PyByteArray_AsString(ro);
        int len = PyLong_AsLong(PyDict_GetItemString(pDict, "fft_result_len"));
        FFT = cJson_Parse(buf);
    //...

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子正

thanks, bro...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值