Ctypes:分配 double** ,将其传递给 C,然后在 Python 中使用它
Ctypes: allocate double** , pass it to C, then use it in Python
编辑 3
我有一些从 python 访问的 C++ 代码(外部为 C)。
我想在python中分配一个double**
,传给C/C++代码复制一个class内部数据的内容,然后在[=]中使用35=] 类似于我使用列表列表的方式。
不幸的是,我无法指定 python 最内部数组的大小,因此在遍历它和程序段错误时它会读取无效内存。
我无法更改 C++ 中的内部数据结构,我希望 python 为我进行绑定检查(就像我使用 c_double_Array_N_Array_M 而不是指针数组)。
test.cpp(用g++ -Wall -fPIC --shared -o test.so test.cpp
编译)
#include <stdlib.h>
#include <string.h>
class Dummy
{
double** ptr;
int e;
int i;
};
extern "C" {
void * get_dummy(int N, int M) {
Dummy * d = new Dummy();
d->ptr = new double*[N];
d->e = N;
d->i = M;
for(int i=0; i<N; ++i)
{
d->ptr[i]=new double[M];
for(int j=0; j <M; ++j)
{
d->ptr[i][j] = i*N + j;
}
}
return d;
}
void copy(void * inst, double ** dest) {
Dummy * d = static_cast<Dummy*>(inst);
for(int i=0; i < d->e; ++i)
{
memcpy(dest[i], d->ptr[i], sizeof(double) * d->i);
}
}
void cleanup(void * inst) {
if (inst != NULL) {
Dummy * d = static_cast<Dummy*>(inst);
for(int i=0; i < d->e; ++i)
{
delete[] d->ptr[i];
}
delete[] d->ptr;
delete d;
}
}
}
Python(这个段错误。把它放在 test.so 所在的同一个目录中)
import os
from contextlib import contextmanager
import ctypes as ct
DOUBLE_P = ct.POINTER(ct.c_double)
library_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test.so')
lib = ct.cdll.LoadLibrary(library_path)
lib.get_dummy.restype = ct.c_void_p
N=15
M=10
@contextmanager
def work_with_dummy(N, M):
dummy = None
try:
dummy = lib.get_dummy(N, M)
yield dummy
finally:
lib.cleanup(dummy)
with work_with_dummy(N,M) as dummy:
internal = (ct.c_double * M)
# Dest is allocated in python, it will live out of the with context and will be deallocated by python
dest = (DOUBLE_P * N)()
for i in range(N):
dest[i] = internal()
lib.copy(dummy, dest)
#dummy is not available anymore here. All the C resources has been cleaned up
for i in dest:
for n in i:
print(n) #it segfaults reading more than the length of the array
我可以在我的 python 代码中更改什么,以便我可以将数组视为列表?
(我只需要阅读它)
3 种将 int** 数组从 Python 传递到 C 并返回
的方法
使得Python迭代时知道数组的大小
数据
此解决方案适用于二维数组或指向数组的指针数组,稍作修改,无需使用像 numpy 这样的库。
我将使用 int 作为类型而不是 double,我们将复制源代码,它被定义为
N = 10;
M = 15;
int ** source = (int **) malloc(sizeof(int*) * N);
for(int i=0; i<N; ++i)
{
source[i] = (int *) malloc(sizeof(int) * M);
for(int j=0; j<M; ++j)
{
source[i][j] = i*N + j;
}
}
1) 分配数组指针
Python分配
dest = ((ctypes.c_int * M) * N) ()
int_P = ctypes.POINTER(ctypes.c_int)
temp = (int_P * N) ()
for i in range(N):
temp[i] = dest[i]
lib.copy(temp)
del temp
# temp gets collected by GC, but the data was stored into the memory allocated by dest
# You can now access dest as if it was a list of lists
for row in dest:
for item in row:
print(item)
C复制函数
void copy(int** dest)
{
for(int i=0; i<N; ++i)
{
memcpy(dest[i], source[i], sizeof(int) * M);
}
}
说明
我们首先分配一个二维数组。 2D array[N][M]
被分配为 1D array[N*M]
,2d_array[n][m] == 1d_array[n*M + m]
。
由于我们的代码需要 int**
,但我们的二维数组分配为 int *
,我们创建一个临时数组来提供预期的结构。
我们分配 temp[N][M]
,然后分配我们之前分配的内存地址 temp[n] = 2d_array[n] = &1d_array[n*M]
(第二个等号表示我们分配的实际内存发生了什么)。
如果您更改复制代码使其复制多于 M,比方说 M+1,您将看到它不会发生段错误,但它会覆盖下一行的内存,因为它们是连续的(如果你更改复制代码,记得将python中分配的dest的大小增加1,否则在最后一行的最后一项之后写入会出现段错误)
2) 切片指针
Python分配
int_P = ctypes.POINTER(ctypes.c_int)
inner_array = (ctypes.c_int * M)
dest = (int_P * N) ()
for i in range(N):
dest[i] = inner_array()
lib.copy(dest)
for row in dest:
# Python knows the length of dest, so everything works fine here
for item in row:
# Python doesn't know that row is an array, so it will continue to read memory without ever stopping (actually, a segfault will stop it)
print(item)
dest = [internal[:M] for internal in dest]
for row in dest:
for item in row:
# No more segfaulting, as now python know that internal is M item long
print(item)
C复制函数
Same as for solution 1
说明
这次我们分配一个实际的数组指针数组,就像分配源一样。
由于最外面的数组 ( dest ) 是一个指针数组,python 不知道指向的数组的长度(它甚至不知道那是一个数组,它可能是一个指向单个 int 的指针)。
如果您遍历该指针,python 将不会进行绑定检查,它会开始读取您的所有内存,从而导致段错误。
因此,我们将指针切片为前 M 个元素(实际上是数组中的所有元素)。现在 python 知道它应该只迭代前 M 个元素,并且不会再出现段错误。
我相信 python 使用此方法复制指向新列表的内容 (see sources)
2.1) 切片指针,继续
Eryksun 在评论中加入并提出了一个解决方案,可以避免复制新列表中的所有元素。
Python分配
int_P = ctypes.POINTER(ctypes.c_int)
inner_array = (ctypes.c_int * M)
inner_array_P = ctypes.POINTER(inner_array)
dest = (int_P * N) ()
for i in range(N):
dest[i] = inner_array()
lib.copy(dest)
dest_arrays = [inner_array_p.from_buffer(x)[0] for x in dest]
for row in dest_arrays:
for item in row:
print(item)
C复制代码
Same as for solution 1
3) 连续内存
只有在C端可以更改复制代码的情况下才可以使用此方法。 source
不需要更改。
Python分配
dest = ((ctypes.c_int * M) * N) ()
lib.copy(dest)
for row in dest:
for item in row:
print(item)
C复制函数
void copy(int * dest) {
for(int i=0; i < N; ++i)
{
memcpy(&dest[i * M], source[i], sizeof(int) * M);
}
}
说明
这一次,就像 1)
的情况一样,我们正在分配一个连续的二维数组。但由于我们可以更改 C 代码,我们不需要创建不同的数组并复制指针,因为我们将向 C 提供预期的类型。
在复制函数中,我们传递每行第一项的地址,复制该行的M个元素,然后转到下一行。
复制模式与 1)
中的完全相同,但是这次我们没有在 python 中编写接口,以便 C 代码按预期方式接收数据,而是更改了 C期望数据采用这种精确格式的代码。
如果保留此 C 代码,您也可以使用 numpy 数组,因为它们是二维行主数组。
感谢@eryksun 在原始问题下方的精彩(简洁)评论,所有这些答案都是可能的。
编辑 3
我有一些从 python 访问的 C++ 代码(外部为 C)。
我想在python中分配一个double**
,传给C/C++代码复制一个class内部数据的内容,然后在[=]中使用35=] 类似于我使用列表列表的方式。
不幸的是,我无法指定 python 最内部数组的大小,因此在遍历它和程序段错误时它会读取无效内存。
我无法更改 C++ 中的内部数据结构,我希望 python 为我进行绑定检查(就像我使用 c_double_Array_N_Array_M 而不是指针数组)。
test.cpp(用g++ -Wall -fPIC --shared -o test.so test.cpp
编译)
#include <stdlib.h>
#include <string.h>
class Dummy
{
double** ptr;
int e;
int i;
};
extern "C" {
void * get_dummy(int N, int M) {
Dummy * d = new Dummy();
d->ptr = new double*[N];
d->e = N;
d->i = M;
for(int i=0; i<N; ++i)
{
d->ptr[i]=new double[M];
for(int j=0; j <M; ++j)
{
d->ptr[i][j] = i*N + j;
}
}
return d;
}
void copy(void * inst, double ** dest) {
Dummy * d = static_cast<Dummy*>(inst);
for(int i=0; i < d->e; ++i)
{
memcpy(dest[i], d->ptr[i], sizeof(double) * d->i);
}
}
void cleanup(void * inst) {
if (inst != NULL) {
Dummy * d = static_cast<Dummy*>(inst);
for(int i=0; i < d->e; ++i)
{
delete[] d->ptr[i];
}
delete[] d->ptr;
delete d;
}
}
}
Python(这个段错误。把它放在 test.so 所在的同一个目录中)
import os
from contextlib import contextmanager
import ctypes as ct
DOUBLE_P = ct.POINTER(ct.c_double)
library_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test.so')
lib = ct.cdll.LoadLibrary(library_path)
lib.get_dummy.restype = ct.c_void_p
N=15
M=10
@contextmanager
def work_with_dummy(N, M):
dummy = None
try:
dummy = lib.get_dummy(N, M)
yield dummy
finally:
lib.cleanup(dummy)
with work_with_dummy(N,M) as dummy:
internal = (ct.c_double * M)
# Dest is allocated in python, it will live out of the with context and will be deallocated by python
dest = (DOUBLE_P * N)()
for i in range(N):
dest[i] = internal()
lib.copy(dummy, dest)
#dummy is not available anymore here. All the C resources has been cleaned up
for i in dest:
for n in i:
print(n) #it segfaults reading more than the length of the array
我可以在我的 python 代码中更改什么,以便我可以将数组视为列表? (我只需要阅读它)
3 种将 int** 数组从 Python 传递到 C 并返回
的方法使得Python迭代时知道数组的大小
数据
此解决方案适用于二维数组或指向数组的指针数组,稍作修改,无需使用像 numpy 这样的库。
我将使用 int 作为类型而不是 double,我们将复制源代码,它被定义为
N = 10;
M = 15;
int ** source = (int **) malloc(sizeof(int*) * N);
for(int i=0; i<N; ++i)
{
source[i] = (int *) malloc(sizeof(int) * M);
for(int j=0; j<M; ++j)
{
source[i][j] = i*N + j;
}
}
1) 分配数组指针
Python分配
dest = ((ctypes.c_int * M) * N) ()
int_P = ctypes.POINTER(ctypes.c_int)
temp = (int_P * N) ()
for i in range(N):
temp[i] = dest[i]
lib.copy(temp)
del temp
# temp gets collected by GC, but the data was stored into the memory allocated by dest
# You can now access dest as if it was a list of lists
for row in dest:
for item in row:
print(item)
C复制函数
void copy(int** dest)
{
for(int i=0; i<N; ++i)
{
memcpy(dest[i], source[i], sizeof(int) * M);
}
}
说明
我们首先分配一个二维数组。 2D array[N][M]
被分配为 1D array[N*M]
,2d_array[n][m] == 1d_array[n*M + m]
。
由于我们的代码需要 int**
,但我们的二维数组分配为 int *
,我们创建一个临时数组来提供预期的结构。
我们分配 temp[N][M]
,然后分配我们之前分配的内存地址 temp[n] = 2d_array[n] = &1d_array[n*M]
(第二个等号表示我们分配的实际内存发生了什么)。
如果您更改复制代码使其复制多于 M,比方说 M+1,您将看到它不会发生段错误,但它会覆盖下一行的内存,因为它们是连续的(如果你更改复制代码,记得将python中分配的dest的大小增加1,否则在最后一行的最后一项之后写入会出现段错误)
2) 切片指针
Python分配
int_P = ctypes.POINTER(ctypes.c_int)
inner_array = (ctypes.c_int * M)
dest = (int_P * N) ()
for i in range(N):
dest[i] = inner_array()
lib.copy(dest)
for row in dest:
# Python knows the length of dest, so everything works fine here
for item in row:
# Python doesn't know that row is an array, so it will continue to read memory without ever stopping (actually, a segfault will stop it)
print(item)
dest = [internal[:M] for internal in dest]
for row in dest:
for item in row:
# No more segfaulting, as now python know that internal is M item long
print(item)
C复制函数
Same as for solution 1
说明
这次我们分配一个实际的数组指针数组,就像分配源一样。
由于最外面的数组 ( dest ) 是一个指针数组,python 不知道指向的数组的长度(它甚至不知道那是一个数组,它可能是一个指向单个 int 的指针)。
如果您遍历该指针,python 将不会进行绑定检查,它会开始读取您的所有内存,从而导致段错误。
因此,我们将指针切片为前 M 个元素(实际上是数组中的所有元素)。现在 python 知道它应该只迭代前 M 个元素,并且不会再出现段错误。
我相信 python 使用此方法复制指向新列表的内容 (see sources)
2.1) 切片指针,继续
Eryksun 在评论中加入并提出了一个解决方案,可以避免复制新列表中的所有元素。
Python分配
int_P = ctypes.POINTER(ctypes.c_int)
inner_array = (ctypes.c_int * M)
inner_array_P = ctypes.POINTER(inner_array)
dest = (int_P * N) ()
for i in range(N):
dest[i] = inner_array()
lib.copy(dest)
dest_arrays = [inner_array_p.from_buffer(x)[0] for x in dest]
for row in dest_arrays:
for item in row:
print(item)
C复制代码
Same as for solution 1
3) 连续内存
只有在C端可以更改复制代码的情况下才可以使用此方法。 source
不需要更改。
Python分配
dest = ((ctypes.c_int * M) * N) ()
lib.copy(dest)
for row in dest:
for item in row:
print(item)
C复制函数
void copy(int * dest) {
for(int i=0; i < N; ++i)
{
memcpy(&dest[i * M], source[i], sizeof(int) * M);
}
}
说明
这一次,就像 1)
的情况一样,我们正在分配一个连续的二维数组。但由于我们可以更改 C 代码,我们不需要创建不同的数组并复制指针,因为我们将向 C 提供预期的类型。
在复制函数中,我们传递每行第一项的地址,复制该行的M个元素,然后转到下一行。
复制模式与 1)
中的完全相同,但是这次我们没有在 python 中编写接口,以便 C 代码按预期方式接收数据,而是更改了 C期望数据采用这种精确格式的代码。
如果保留此 C 代码,您也可以使用 numpy 数组,因为它们是二维行主数组。
感谢@eryksun 在原始问题下方的精彩(简洁)评论,所有这些答案都是可能的。