Python ctypes定义C结构体
我正在尝试调用一些由Matlab编码器生成的C代码。Matlab使用一种叫做emxArray的C结构来表示矩阵(详细信息可以在这里找到:http://www.mathworks.co.uk/help/fixedpoint/ug/c-code-interface-for-unbounded-arrays-and-structure-fields.html)。
struct emxArray_real_T
{
double *data;
int *size;
int allocatedSize;
int numDimensions;
boolean_T canFreeData;
};
我对ctypes的经验不多,正在努力创建一个等效的结构,以便可以用来在C的.so文件中定义的函数之间传递向量。
这是我在Python中目前的进展……
class EmxArray(ctypes.Structure):
""" creates a struct to match emxArray_real_T """
_fields_ = [('data', ctypes.POINTER(ctypes.c_double)),
('size', ctypes.POINTER(ctypes.c_int)),
('allocatedSize', ctypes.c_int),
('numDimensions', ctypes.c_int),
('canFreeData', ctypes.c_bool)]
不过,如果我这样定义:
data = (1.1, 1.2, 1.3, 1.4)
L = len(data)
x = EmxArray()
x.data = (ctypes.c_double * L)(*data)
x.data = (ctypes.c_int * 1)(L)
那么这个就能工作了
print len(x.data[:L])
for v in x.data[:L]: print v
编辑:我整理了一下,并采纳了Roland的建议,可以使用以下方式提取数据
data_out = x.data[:L]
我需要进一步调查,看看是否能成功使用这个结构来从C代码中传递和接收数据。
解决方案
按照Roland的建议实现ctypes结构并没有成功——返回的值是垃圾数据,我从未弄清楚原因,因为我追随了lilbil的答案,采用了基于Python的实现。我接受了那个答案,因为它是最接近的……
我将在这里记录我的解决方案,因为这可能会帮助其他人节省和我一样多的时间。
首先,我生成了一个简单的Matlab函数,它将函数的每个元素与自身相乘,并使用编码器将其编译成C的.so文件。这个文件通过ctypes导入到Python中。代码如下……
import ctypes
LIBTEST = '..../dll/emx_test/'
EMX = ctypes.cdll.LoadLibrary(LIBTEST + 'emx_test.so')
init = EMX.emx_test_initialize()
# Create a data structure to hold the pointer generated by emxCreateWrapper...
class Opaque(ctypes.Structure):
pass
# make some random data to pass in
data_in = [1., 2., 4., 8., 16.]
L = len(data_in)
# create an empty array of the same size for the output
data_ou = [0] * L
# put this in a ctypes array
ina = (ctypes.c_double * L)(*data_in)
oua = (ctypes.c_double * L)(*data_ou)
# create a pointer for these arrays & set the rows and columns of the matrix
inp = ctypes.pointer(ina)
oup = ctypes.pointer(oua)
nrows = ctypes.c_int(1)
ncols = ctypes.c_int(L)
# use EMX.emxCreateWrapper_real_T(double *data, int rows, int cols) to generate an emx wrapping the data
# input arg types are a pointer to the data NOTE its not great to have to resize the ctypes.c_double but cant see another way
EMX.emxCreateWrapper_real_T.argtypes = [ctypes.POINTER(ctypes.c_double * L), ctypes.c_int, ctypes.c_int]
# a pointer to the emxArray is returned and stored in Opaque
EMX.emxCreateWrapper_real_T.restype = ctypes.POINTER(Opaque)
# use emxCreateWrapper
in_emx = EMX.emxCreateWrapper_real_T(inp, nrows, ncols)
ou_emx = EMX.emxCreateWrapper_real_T(oup, nrows, ncols)
# so now we have to emx's created and have pointers to them we can run the emx_test
# emx test looks like this in matlab
#
# function res = emx_test ( in )
# res = in .* in;
# end
#
# so basically it multiplies each element of the matrix by itself
#
# therefore [1., 2., 4., 8., 16.] should become [1., 4., 8., 64., 256.]
EMX.emx_test(in_emx, ou_emx)
# and voila...that's what we get
print 'In: ', ina[:L]
print 'Out:', oua[:L]
输出:
In: [1.0, 2.0, 4.0, 8.0, 16.0]
Out:[1.0, 4.0, 16.0, 64.0, 256.0]
感谢大家的时间和建议。
2 个回答
只需要创建一个指针,然后再给它赋值数据就可以了。
import ctypes
class EmxArray(ctypes.Structure):
""" creates a struct to match emxArray_real_T """
_fields_ = [('data', ctypes.POINTER(ctypes.c_double)),
('size', ctypes.POINTER(ctypes.c_int)),
('allocatedSize', ctypes.c_int),
('numDimensions', ctypes.c_int),
('canFreeData', ctypes.c_bool)]
data = (1.3, 3.5, 2.7, 4.1)
L = len(data)
e = EmxArray()
e.data = (ctypes.c_double * L)(*data)
e.size = (ctypes.c_int * 1)(L)
# et cetera
我对Python和C语言的接口不是很熟悉,所以我说的可能不太准确。我的猜测是,崩溃的原因可能是因为x->data
从来没有被初始化,而它指向的内存也没有分配。
我在处理从其他语言(比如Android Java)调用MATLAB Coder生成的代码时,通常会手动编写一个C语言接口函数,这样可以提供一个更简单的使用方法。这可以减轻在其他环境中构建emxArray
的负担。如果生成的函数foo
需要一个二维的double
数组作为输入和输出,那么可以这样做:
void foo(double *x, int *szx, double **y, int *szy);
这个函数会接收输入数据的指针和大小,并提供输出数据的指针和大小。它的实现大概是这样的:
void foo(double *x, int *szx, double **y, int *szy)
{
emxArray_real_T *pEmx;
emxArray_real_T *pEmy;
/* Create input emxArray assuming 2-dimensional input */
pEmx = emxCreateWrapper_real_T(x, szx[0], szx[1]);
/* Create output emxArray (assumes that the output is not */
/* written before allocation occurs) assuming 2-D output */
pEmy = emxCreateWrapper_real_T(NULL, 0, 0);
/* Call generated code (call foobar_initialize/terminate elsewhere) */
foobar(pEmx, pEmy);
/* Unpack result - You may want to MALLOC storage in *y and */
/* MEMCPY there alternatively */
*y = pEmy->data;
szy[0] = pEmy->size[0];
szy[1] = pEmy->size[1];
/* Clean up any memory allocated in the emxArrays (e.g. the size vectors) */
emxDestroyArray_real_T(pEmx);
emxDestroyArray_real_T(pEmy);
}
你应该可以更简单地从Python调用这个函数,并根据需要传入想要的数据。
我另外的回答中有关于emxArray_*
函数的更多细节,这些函数在文件foobar_emxAPI.h
中可以找到。