【问题标题】:Python CFFI - Unable to use formatted Python string as byte-array in function callPython CFFI - 无法在函数调用中使用格式化的 Python 字符串作为字节数组
【发布时间】:2016-11-11 22:27:30
【问题描述】:

我正在学习如何将C 编写的代码包含在Python 中的各种方法,因为我有一个用于 Microchip 设备的 API,它非常......使用起来很乏味,我想让我的生活通过为其添加Python 包装器,将来会更容易,这将使我能够更快地测试东西。一种方法是使用cffi 模块,它甚至为用户提供verify(),它基本上调用C 编译器来检查提供的cdef(...) 是否正确。

我写了一个小项目,以便我可以首先学习如何正确使用cffi。它由两部分组成

  1. - 用 C 编写。我使用 cmakemake 相应地编译它的代码:

    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;
    }
    
  2. 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 structlabel 参数的值,要么得到一个奇怪的交替垃圾数据(主要是既不是字母也不是数字的奇怪字符)。

我认为我错误地使用了 encode() 函数,但是当我在 Python 交互式 shell 中对其进行测试时,我得到了与使用 b'...' 相同的输出。

知道这里发生了什么吗?


一个很好知道的问题: 从我目前所读到的内容看来,cffi 使用 Python 中的垃圾收集来释放 C 中动态分配的内存代码。我已经用一堆点测试了它,但我想确保这实际上总是如此。


更新: 好的,所以看起来没有new(...) 的东西确实有效,但是在这种情况下,所有值都与循环中的最后一个值相同。例如,如果循环达到 10,那么所有 struct Python 对象的标签中都会包含 10。这似乎是一个参考问题。当我使用new(...) 时,我得到了垃圾数据。

【问题讨论】:

    标签: python c python-cffi


    【解决方案1】:

    在您的 C 代码中,point_t 结构包含在 labelchar * 中,即指向内存中其他位置的指针。如果您创建 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,就必须确保该列表一直存在。

    【讨论】:

    • :O 完全正确,我完全错过了这一点,即使我在文档中读了 1000 次。大声笑所以基本上在创建基于 C 的 API 的包装器时,必须创建特殊对象,以便更好地管理此类引用。太感谢了。在我努力解决这个问题的过程中,我在这里长出了一些白毛。
    猜你喜欢
    • 2021-05-10
    • 2012-05-31
    • 2013-08-27
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    • 2021-12-06
    相关资源
    最近更新 更多