问题 如何使用malloc和free与python ctypes?


我说我的C库中有一个函数 runsim() 它将指针指向 struct repdata 作为其中一个论点,在哪里 struct repdata 是(谁)给的

struct repdata {
    int *var1;
    int *var2;
    int *var3;
    char *var4;
    double *var5;
    double *var6;
    int *var7;
};

当使用C exclusive时,我初始化一个类型的变量 struct repdata 调用函数,

struct repdata data;
void create_data_container(struct repdata *data, int len_data)
{

    data -> var1 = malloc( sizeof(int) * len_data );
    data -> var2 = malloc( sizeof(int) * len_data );
    data -> var3 = malloc( sizeof(int) * len_data );
    data -> var4 = malloc( sizeof(char) * len_data );
    data -> var5 = malloc( sizeof(double) * len_data);
    data -> var6 = malloc( sizeof(double) * len_data);
    data -> var7 = malloc( sizeof(int) * len_data);
}

然后在模拟过程中填充此结构。在将数据写入文件后,我使用标准释放内存

free(data.var1);
free(data.var2);
.
.
.
free(data.var7);

我想打电话给 runsim() Python使用Python Ctypes的函数。为此,我需要传递一个指向变量的指针(相当于一个变量的类型) struct repdata)作为其中之一 runsim() 参数。假设在Python中我定义了一个等价的 struct repdata 以下列方式。

import ctypes as C

class Repdata(C.Structure):
    _fields_ = [
        ("var1", C.POINTER(C.c_int)),
        ("var2", C.POINTER(C.c_int)),
        ("var3", C.POINTER(C.c_int)),
        ("var4", C.POINTER(C.c_char)),
        ("var5", C.POINTER(C.c_double)),
        ("var6", C.POINTER(C.c_double)),
        ("var7", C.POINTER(C.c_int)),
    ]

相当于什么 create_data_container 上面显示的函数在Python端?我想初始化一个Repdata实例,它可以传递给C代码,并有足够的内存来存储复制数据。并且,一旦模拟完成,我如何从Python中释放内存?

我使用的是Ubuntu Linux 12.04。

在此先感谢您的帮助。


3198
2017-09-08 00:39


起源



答案:


您可以使用分配缓冲区 ctypes 并将它们分配给指针。一旦Python ctypes对象没有引用,它们将自动释放。这是一个简单的例子(使用Windows DLL ...没有Linux机器,但想法是相同的)和Python包装器。

create_string_buffer 分配一个可写的缓冲区,可以从Python传递给C ctypes 将作为一个元帅 char*

您还可以创建可写数组 ctypes 语法类型:

variable_name = (ctypes_type * length)(initial_values)

x.h

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

struct example {
    char* data;
    int len;          // of data buffer
    double* doubles;
    int count;        // of doubles
};

DLL_API void func(struct example* p);

x.c

#include <stdio.h>
#define DLL_EXPORTS
#include "x.h"

void func(struct example* p)
{
    int i;
    strcpy_s(p->data,p->len,"hello, world!");
    for(i = 0; i < p->count; i++)
        p->doubles[i] = 1.1 * (i + 1);
}

x.py

import ctypes

class Example(ctypes.Structure):

    _fields_ = [
        ('data',ctypes.POINTER(ctypes.c_char)),
        ('len',ctypes.c_int),
        ('doubles',ctypes.POINTER(ctypes.c_double)),
        ('count',ctypes.c_int)]

    def __init__(self,length,count):
        self.data = ctypes.cast(ctypes.create_string_buffer(length),ctypes.POINTER(ctypes.c_char))
        self.len = length
        self.doubles = (ctypes.c_double * count)()
        self.count = count

    def __repr__(self):
        return 'Example({},[{}])'.format(
            ctypes.string_at(self.data),
            ','.join(str(self.doubles[i]) for i in range(self.count)))

class Dll:

    def __init__(self):
        self.dll = ctypes.CDLL('x')
        self.dll.func.argtypes = [ctypes.POINTER(Example)]
        self.dll.func.restype = None

    def func(self,ex):
        self.dll.func(ctypes.byref(ex))

d = Dll()
e = Example(20,5)
print('before:',e)
d.func(e)
print ('after:',e)

产量

before: Example(b'',[0.0,0.0,0.0,0.0,0.0])
after: Example(b'hello, world!',[1.1,2.2,3.3000000000000003,4.4,5.5])

10
2017-09-08 01:33



我可能错了,但这似乎与我正在寻找的不同。在我的问题中,数据是在C代码中生成的,我想将一个容器传递给Python中的C代码,该代码可以填充该数据。在这里的答案中,数据正从Python传递给C. - Curious2learn
@ Curious2learn,缓冲区已分配,但不必在Python端初始化。您也可以在C端填写它们,数据将显示在Python端。我会改变上面的内容来证明这一点。 - Mark Tolonen
@MarkTolonen来你的回答:我可以“注入” create_string_buffer 作为一个 malloc 通过我的c库替换 CFUNCTYPE(c_void_p, c_size_t)(create_string_buffer)?我想避免“询问”我的c库一直需要多少内存...... - sebastian


答案:


您可以使用分配缓冲区 ctypes 并将它们分配给指针。一旦Python ctypes对象没有引用,它们将自动释放。这是一个简单的例子(使用Windows DLL ...没有Linux机器,但想法是相同的)和Python包装器。

create_string_buffer 分配一个可写的缓冲区,可以从Python传递给C ctypes 将作为一个元帅 char*

您还可以创建可写数组 ctypes 语法类型:

variable_name = (ctypes_type * length)(initial_values)

x.h

#ifdef DLL_EXPORTS
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

struct example {
    char* data;
    int len;          // of data buffer
    double* doubles;
    int count;        // of doubles
};

DLL_API void func(struct example* p);

x.c

#include <stdio.h>
#define DLL_EXPORTS
#include "x.h"

void func(struct example* p)
{
    int i;
    strcpy_s(p->data,p->len,"hello, world!");
    for(i = 0; i < p->count; i++)
        p->doubles[i] = 1.1 * (i + 1);
}

x.py

import ctypes

class Example(ctypes.Structure):

    _fields_ = [
        ('data',ctypes.POINTER(ctypes.c_char)),
        ('len',ctypes.c_int),
        ('doubles',ctypes.POINTER(ctypes.c_double)),
        ('count',ctypes.c_int)]

    def __init__(self,length,count):
        self.data = ctypes.cast(ctypes.create_string_buffer(length),ctypes.POINTER(ctypes.c_char))
        self.len = length
        self.doubles = (ctypes.c_double * count)()
        self.count = count

    def __repr__(self):
        return 'Example({},[{}])'.format(
            ctypes.string_at(self.data),
            ','.join(str(self.doubles[i]) for i in range(self.count)))

class Dll:

    def __init__(self):
        self.dll = ctypes.CDLL('x')
        self.dll.func.argtypes = [ctypes.POINTER(Example)]
        self.dll.func.restype = None

    def func(self,ex):
        self.dll.func(ctypes.byref(ex))

d = Dll()
e = Example(20,5)
print('before:',e)
d.func(e)
print ('after:',e)

产量

before: Example(b'',[0.0,0.0,0.0,0.0,0.0])
after: Example(b'hello, world!',[1.1,2.2,3.3000000000000003,4.4,5.5])

10
2017-09-08 01:33



我可能错了,但这似乎与我正在寻找的不同。在我的问题中,数据是在C代码中生成的,我想将一个容器传递给Python中的C代码,该代码可以填充该数据。在这里的答案中,数据正从Python传递给C. - Curious2learn
@ Curious2learn,缓冲区已分配,但不必在Python端初始化。您也可以在C端填写它们,数据将显示在Python端。我会改变上面的内容来证明这一点。 - Mark Tolonen
@MarkTolonen来你的回答:我可以“注入” create_string_buffer 作为一个 malloc 通过我的c库替换 CFUNCTYPE(c_void_p, c_size_t)(create_string_buffer)?我想避免“询问”我的c库一直需要多少内存...... - sebastian