4

我有一个自制的 C 库,我想使用 python 访问它。问题在于代码基本上由两部分组成,一个是从多个文件中读取数据的初始化,另一个是一些只需要执行一次的计算。另一部分循环调用,重复使用之前生成的数据。对于这个函数,我想从 python 传递参数。

我的想法是编写两个 C 包装函数,“init”和“loop”——“init”读取数据并返回指向“loop”可以使用的结构的 void 指针以及我可以从 python 传递的其他参数。就像是

void *init() {
  struct *mystruct ret = (mystruct *)malloc(sizeof(mystruct));
  /* Fill ret with data */
  return ret;
}

float loop(void *data, float par1, float par2) {
  /* do stuff with data, par1, par2, return result */
}

我尝试从 python 调用“init”作为 c_void_p,但是由于“循环”改变了“数据”的一些内容并且 ctypes 的 void 指针是不可变的,所以这不起作用。

我看到的类似问题的其他解决方案似乎需要了解“init”将使用多少内存,而我不知道。

有没有办法通过 python 将数据从一个 C 函数传递到另一个函数,而无需告诉 python 它到底是什么或多少?还是有其他方法可以解决我的问题?


我尝试(但失败)编写了一个最小的崩溃示例,经过一些调试,结果发现我的 C 代码中有一个错误。感谢所有回复的人!希望这可能对其他人有所帮助,这是一个最小的工作版本(仍然没有单独的“免费” - 抱歉):

pybug.c:

#include <stdio.h>
#include <stdlib.h>

typedef struct inner_struct_s {
  int length;
  float *array;
} inner_struct_t;

typedef struct mystruct_S {
  int id;
  float start;
  float end;
  inner_struct_t *inner;
} mystruct_t;

void init(void **data) {
  int i;
  mystruct_t *mystruct = (mystruct_t *)malloc(sizeof(mystruct_t));
  inner_struct_t *inner = (inner_struct_t *)malloc(sizeof(inner_struct_t));
  inner->length = 10;
  inner->array = calloc(inner->length, sizeof(float));
  for (i=0; i<inner->length; i++)
    inner->array[i] = 2*i;
  mystruct->id = 0;
  mystruct->start = 0;
  mystruct->end = inner->length;
  mystruct->inner = inner;
  *data = mystruct;
}

float loop(void *data, float par1, float par2, int newsize) {
  mystruct_t *str = data;
  inner_struct_t *inner = str->inner;
  int i;
  inner->length = newsize;
  inner->array  = realloc(inner->array, newsize * sizeof(float));
  for (i=0; i<inner->length; i++)
    inner->array[i] = par1 + i * par2;
  return inner->array[inner->length-1];
}

编译为

cc -c -fPIC pybug.c
cc -shared -o libbug.so pybug.o

在python中运行:

from ctypes import *
sl = CDLL('libbug.so')
# What arguments do functions take / return?
sl.init.argtype = c_void_p
sl.loop.restype = c_float
sl.loop.argtypes = [c_void_p, c_float, c_float, c_int]

# Init takes a pointer to a pointer
px = c_void_p()
sl.init(byref(px))

# Call the loop a couple of times
for i in range(10):
    print sl.loop(px, i, 5, 10*i+5)
4

2 回答 2

2

free调用者完成后,您应该对数据缓冲区具有相应的功能。否则我看不到问题。只需将指针传递给loop您从中获得的init.

init.restype = c_void_p
loop.argtypes = [c_void_p, c_float, c_float]
loop.restype = c_float

我不确定你所说的“ctypes' void 指针是不可变的”是什么意思,除非你在谈论c_char_pand c_wchar_p。问题在于,如果您将 Python 字符串作为参数传递,它将使用 Python 的指向字符串缓冲区的私有指针。如果一个函数可以改变字符串,你应该先把它复制到一个c_charc_wchar数组中。

这是一个简单的示例,显示了将 Python 字符串(2.x 字节字符串)作为参数传递给修改它的函数的问题。在这种情况下,它将索引 0 更改为 '\x00':

>>> import os
>>> from ctypes import *
>>> open('tmp.c', 'w').write("void f(char *s) {s[0] = 0;}")
>>> os.system('gcc -shared -fPIC -o tmp.so tmp.c')
0
>>> tmp = CDLL('./tmp.so')
>>> tmp.f.argtypes = [c_void_p]
>>> tmp.f.restype = None
>>> tmp.f('a')
>>> 'a'
'\x00'
>>> s = 'abc' 
>>> tmp.f(s)
>>> s
'\x00bc'

这特定于将 Python 字符串作为参数传递。将指针传递给旨在可变的数据结构,无论是 ctypes 数据对象(例如 a Structure)还是库返回的指针,都不是问题。

于 2013-07-05T18:03:49.820 回答
1

您的 C 代码是否在 DLL 中?如果可以的话可以考虑在那里创建一个全局指针。init() 将执行所需的任何初始化并将指针设置为等于新分配的内存,并且 loop() 将对该内存进行操作。也不要忘记使用 close() 函数释放它

于 2013-07-05T16:40:37.337 回答