Python CFFI - 无法在函数调用中使用格式化的 Python 字符串作为字节数组
Python CFFI - Unable to use formatted Python string as byte-array in function call
我正在学习如何将用 C
编写的代码包含到 Python
中的各种方法,因为我有一个用于 Microchip 设备的 API,这非常...乏味与我一起工作,我想通过为它添加一个 Python
包装器来让我的生活更轻松,这将使我能够更快地测试东西。一种方法是使用 cffi
模块,它甚至为用户提供 verify()
,基本上调用 C
编译器来检查提供的 cdef(...)
是否正确。
我写了一个小项目,让我可以先学习如何正确使用cffi
。它由两部分组成
Library - 用 C 语言编写。我使用 cmake
和 make
相应地编译其代码:
CMakeLists.txt
project(testlib_for_cffi)
cmake_minimum_required(VERSION 2.8)
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
# Debug build
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -g -O0")
# Release build
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os")
aux_source_directory(. SRC_LIST)
add_library(testcffi SHARED ${SRC_LIST})
# Not required for the library but needed if I want to check for memory leaks with Valgrind
set(SRC main.c)
add_executable(${PROJECT_NAME} ${SRC})
target_link_libraries(${PROJECT_NAME} PUBLIC testcffi)
testcffi.h
typedef struct
{
double x;
double y;
double z;
char *label;
} point_t;
// Creation, printing and deletion
point_t* createPoint(double x, double y, double z, char *label);
void printPoint(point_t *point);
void deletePoint(point_t *point);
testcffi.c
#include "testcffi.h"
#include <stdio.h>
#include <malloc.h>
point_t* createPoint(double x, double y, double z, char *label) {
point_t *p = malloc(sizeof(point_t));
p->x = x;
p->y = y;
p->z = z;
p->label = label;
return p;
}
void printPoint(point_t *point) {
if(point == NULL) return;
printf("Data:\n\tx : %f\n\ty : %f\n\tz : %f\n\tmsg : \"%s\"\n", point->x, point->y, point->z, point->label);
}
void deletePoint(point_t *point) {
if(point == NULL) return;
free(point);
point = NULL;
}
Python 中的测试代码 - 该代码演示了 struct
的用法以及上面库中的三个函数:
#!/usr/bin/python3
from cffi import FFI
import random
ffi = FFI()
# Add library's header
ffi.cdef('''
typedef struct
{
double x;
double y;
double z;
char * label;
} point_t;
// Creation, printing and deletion
point_t * createPoint(double x=0., double y=0., double z=0., char *label="my_label");
void printPoint(point_t *point);
void deletePoint(point_t *point);
''')
# Load shared object from subdirectory `build`
CLibTC = ffi.dlopen('build/libtestcffi.so')
def createList(length=5):
if len:
lst = []
for i in range(0, length):
lst.append(CLibTC.createPoint(
float(random.random()*(i+1)*10),
float(random.random()*(i+1)*10),
float(random.random()*(i+1)*10),
b'hello' # FIXME Why does ONLY this work?
# ('point_%d' % i).encode('utf-8') # NOT WORKING
# 'point_{0}'.format(str(i)).encode('utf-8') # NOT WORKING
# ffi.new('char[]', 'point_{0}'.format(str(i)).encode('utf-8')) # NOT WORKING
))
return lst
return None
def printList(lst):
if lst and len(lst):
for l in lst:
CLibTC.printPoint(l)
list_of_dstruct_ptr = createList(10)
printList(list_of_dstruct_ptr)
问题来自字节数组,我必须将我的 Python
字符串转换为字节数组,以便将数据传递到我的 C
代码中的相应位置。
上面的代码有效,但我想使用类似于 b'hello'
的其他字符串。这就是为什么我尝试在 Python
中使用 format()
(连同它的缩写 %
)来组合一堆字母和数字但是。它没有成功。我要么得到 ""
作为 point_t
struct
的 label
参数的值,要么得到奇怪的交替垃圾数据(主要是既不是字母也不是数字的奇怪字符) .
我以为我没有正确使用 encode()
函数,但是当我在我的 Python
交互式 shell 中测试它时,我得到了与使用 b'...'
相同的输出.
知道这里发生了什么吗?
一个很有趣的问题:从我目前所读的内容来看,似乎 cffi
在 Python
中使用了垃圾收集在 C 代码中释放动态分配的内存。我已经用很多点对其进行了测试,但我想确保这实际上总是如此。
更新:
好的,看起来没有 new(...)
的东西确实有效,但是在那种情况下,所有值都与循环中的最后一个值相同。例如,如果循环达到 10,那么所有 struct
Python 对象的标签中都会有 10。这似乎是一个参考问题。当我使用 new(...)
时,我得到了垃圾数据。
在您的 C 代码中,point_t
结构保存在 label
和 char *
中,即指向内存中其他位置的指针。如果您创建 10 个 point_t
结构,它们将保存指向内存中其他地方的 10 个字符串的指针。只要您使用 point_t
结构,您就必须确保这 10 个字符串一直保持活动状态。 CFFI猜不到有这样的关系。当你调用 CLibTC.createPoint(..., some_string)
时,CFFI 在调用周围分配一个 char[]
数组并在其中复制 some_string
,但是这个 char[]
内存在调用后被释放。
改用那种代码:
c_string = ffi.new("char[]", some_string)
lst.append(createPoint(..., c_string))
keepalive.append(c_string)
其中 keepalive
是另一个列表,只要您需要 point_t
包含有效的 label
,您就必须确保该列表一直有效。
我正在学习如何将用 C
编写的代码包含到 Python
中的各种方法,因为我有一个用于 Microchip 设备的 API,这非常...乏味与我一起工作,我想通过为它添加一个 Python
包装器来让我的生活更轻松,这将使我能够更快地测试东西。一种方法是使用 cffi
模块,它甚至为用户提供 verify()
,基本上调用 C
编译器来检查提供的 cdef(...)
是否正确。
我写了一个小项目,让我可以先学习如何正确使用cffi
。它由两部分组成
Library - 用 C 语言编写。我使用
cmake
和make
相应地编译其代码:CMakeLists.txt
project(testlib_for_cffi) cmake_minimum_required(VERSION 2.8) set(CMAKE_BUILD_TYPE Release) set(CMAKE_CXX_FLAGS "-fPIC ${CMAKE_C_FLAGS}") # Debug build set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -g -O0") # Release build set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os") aux_source_directory(. SRC_LIST) add_library(testcffi SHARED ${SRC_LIST}) # Not required for the library but needed if I want to check for memory leaks with Valgrind set(SRC main.c) add_executable(${PROJECT_NAME} ${SRC}) target_link_libraries(${PROJECT_NAME} PUBLIC testcffi)
testcffi.h
typedef struct { double x; double y; double z; char *label; } point_t; // Creation, printing and deletion point_t* createPoint(double x, double y, double z, char *label); void printPoint(point_t *point); void deletePoint(point_t *point);
testcffi.c
#include "testcffi.h" #include <stdio.h> #include <malloc.h> point_t* createPoint(double x, double y, double z, char *label) { point_t *p = malloc(sizeof(point_t)); p->x = x; p->y = y; p->z = z; p->label = label; return p; } void printPoint(point_t *point) { if(point == NULL) return; printf("Data:\n\tx : %f\n\ty : %f\n\tz : %f\n\tmsg : \"%s\"\n", point->x, point->y, point->z, point->label); } void deletePoint(point_t *point) { if(point == NULL) return; free(point); point = NULL; }
Python 中的测试代码 - 该代码演示了
struct
的用法以及上面库中的三个函数:#!/usr/bin/python3 from cffi import FFI import random ffi = FFI() # Add library's header ffi.cdef(''' typedef struct { double x; double y; double z; char * label; } point_t; // Creation, printing and deletion point_t * createPoint(double x=0., double y=0., double z=0., char *label="my_label"); void printPoint(point_t *point); void deletePoint(point_t *point); ''') # Load shared object from subdirectory `build` CLibTC = ffi.dlopen('build/libtestcffi.so') def createList(length=5): if len: lst = [] for i in range(0, length): lst.append(CLibTC.createPoint( float(random.random()*(i+1)*10), float(random.random()*(i+1)*10), float(random.random()*(i+1)*10), b'hello' # FIXME Why does ONLY this work? # ('point_%d' % i).encode('utf-8') # NOT WORKING # 'point_{0}'.format(str(i)).encode('utf-8') # NOT WORKING # ffi.new('char[]', 'point_{0}'.format(str(i)).encode('utf-8')) # NOT WORKING )) return lst return None def printList(lst): if lst and len(lst): for l in lst: CLibTC.printPoint(l) list_of_dstruct_ptr = createList(10) printList(list_of_dstruct_ptr)
问题来自字节数组,我必须将我的 Python
字符串转换为字节数组,以便将数据传递到我的 C
代码中的相应位置。
上面的代码有效,但我想使用类似于 b'hello'
的其他字符串。这就是为什么我尝试在 Python
中使用 format()
(连同它的缩写 %
)来组合一堆字母和数字但是。它没有成功。我要么得到 ""
作为 point_t
struct
的 label
参数的值,要么得到奇怪的交替垃圾数据(主要是既不是字母也不是数字的奇怪字符) .
我以为我没有正确使用 encode()
函数,但是当我在我的 Python
交互式 shell 中测试它时,我得到了与使用 b'...'
相同的输出.
知道这里发生了什么吗?
一个很有趣的问题:从我目前所读的内容来看,似乎 cffi
在 Python
中使用了垃圾收集在 C 代码中释放动态分配的内存。我已经用很多点对其进行了测试,但我想确保这实际上总是如此。
更新:
好的,看起来没有 new(...)
的东西确实有效,但是在那种情况下,所有值都与循环中的最后一个值相同。例如,如果循环达到 10,那么所有 struct
Python 对象的标签中都会有 10。这似乎是一个参考问题。当我使用 new(...)
时,我得到了垃圾数据。
在您的 C 代码中,point_t
结构保存在 label
和 char *
中,即指向内存中其他位置的指针。如果您创建 10 个 point_t
结构,它们将保存指向内存中其他地方的 10 个字符串的指针。只要您使用 point_t
结构,您就必须确保这 10 个字符串一直保持活动状态。 CFFI猜不到有这样的关系。当你调用 CLibTC.createPoint(..., some_string)
时,CFFI 在调用周围分配一个 char[]
数组并在其中复制 some_string
,但是这个 char[]
内存在调用后被释放。
改用那种代码:
c_string = ffi.new("char[]", some_string)
lst.append(createPoint(..., c_string))
keepalive.append(c_string)
其中 keepalive
是另一个列表,只要您需要 point_t
包含有效的 label
,您就必须确保该列表一直有效。