使用 ctypes 传递结构指针
Posted
技术标签:
【中文标题】使用 ctypes 传递结构指针【英文标题】:Using ctypes to pass around struct pointers 【发布时间】:2014-08-29 21:42:09 【问题描述】:所以到目前为止我所做的是构建一个小的 ctypes 和 python 代码,它执行以下操作:
Python 以指向 void 指针的指针作为参数调用 C 函数。
C 代码创建一个ReturnStruct
类型的结构并实例化它及其数据成员,然后将python 传入的指针设置为指向该结构。
Python 多次调用另一个 C 函数来增加某些值。
Python 然后检查这些值。
Python 调用一个 C 函数来释放结构指针。
到目前为止,我已经完成了前 3 个阶段,但我在后两个部分遇到了问题。这是C代码:
#include <stdio.h>
#include <stdlib.h>
//#include "runSolver.h"
#define SMB_MAX_DATA_SIZE 16
typedef struct testStruct
double *x[11];
double *u[10];
Test;
typedef struct returnStruct_t
Test* vars;
ReturnStruct;
void initalize_returnStruct(void** returnStruct)
ReturnStruct* new_returnStruct = (ReturnStruct *)malloc(sizeof(ReturnStruct));
Test* varsStruct = (Test*)malloc(sizeof(Test)*3);
int dataSize = 5;
int i;
for(i = 0; i < 3; i++)
int x;
for(x = 0; x < 11; x++)
varsStruct[i].x[x] = (double *)malloc(sizeof(double)*5);
for(x = 0; x < 10; x++)
varsStruct[i].u[x] = (double *)malloc(sizeof(double)*5);
new_returnStruct->vars = varsStruct;
*returnStruct = new_returnStruct;
void free_returnStruct(void* data)
ReturnStruct* returnStruct = data;
int i;
for(i = 0; i < 3; i++)
int x;
for(x = 1; x < 11; x++)
free(returnStruct->vars[i].x[x]);
for(x = 0; x < 10; x++)
free(returnStruct->vars[i].u[x]);
free(returnStruct->vars);
free(returnStruct);
void parallelSolver(void* data)
ReturnStruct* VarsArray = data;
fprintf(stderr, " This is the value: %f \n", VarsArray->vars[0].x[0][0]);
fprintf(stderr, " This is the value: %f \n", VarsArray->vars[0].x[10][4]);
fprintf(stderr, " This is the value: %f \n", VarsArray->vars[0].u[0][0]);
fprintf(stderr, " This is the value: %f \n", VarsArray->vars[0].u[9][2]);
VarsArray->vars[0].x[0][0] += 20.0;
VarsArray->vars[0].x[10][4] += 203.0;
VarsArray->vars[0].u[0][0] += 202.0;
VarsArray->vars[0].u[9][2] += 202.0;
这里是python代码:
#!/usr/bin/python
import sys
import ctypes as ct
numOpt = 3
class vars_t(ct.Structure):
_fields_ = [("u", ct.POINTER(ct.c_double*10)),
("x", ct.POINTER(ct.c_double*11))]
class returnStruct_t(ct.Structure):
_fields_ = [("vars", vars_t*numOpt)]
runSolver = ct.CDLL('./runSolverParallel.so')
returnStructPointer = ct.POINTER(returnStruct_t)
runSolver.parallelSolver.argtypes = [ct.c_void_p()]
varsd = ct.c_void_p()
runSolver.initalize_returnStruct(ct.byref(varsd))
runSolver.parallelSolver(varsd)
runSolver.parallelSolver(varsd)
runSolver.parallelSolver(varsd)
runSolver.parallelSolver(varsd)
varsdb = ct.cast(varsd, returnStruct_t)
print(varsdb.contents.vars[0].x[0][0])
runSolver.free_returnStruct(varsd)
代码运行良好,直到我到达这三行:
varsdb = ct.cast(varsd, returnStruct_t)
print(varsdb.contents.vars[0].x[0][0])
runSolver.free_returnStruct(varsd)
所有这些都会产生段错误。任何有关如何使其正常工作的建议将不胜感激!
错误如下所示:
Starting program: /usr/bin/python UserDefinedCode.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
This is the value: 0.000000
This is the value: 0.000000
This is the value: 0.000000
This is the value: 0.000000
This is the value: 20.000000
This is the value: 203.000000
This is the value: 202.000000
This is the value: 202.000000
This is the value: 40.000000
This is the value: 406.000000
This is the value: 404.000000
This is the value: 404.000000
This is the value: 60.000000
This is the value: 609.000000
This is the value: 606.000000
This is the value: 606.000000
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff33795d4 in ?? () from /usr/lib/python2.7/lib-dynload/_ctypes.so
(gdb) where
#0 0x00007ffff33795d4 in ?? () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#1 0x00007ffff3386ea4 in ffi_call_unix64 () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#2 0x00007ffff33868c5 in ffi_call () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#3 0x00007ffff33772c2 in _ctypes_callproc () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#4 0x00007ffff3377aa2 in ?? () from /usr/lib/python2.7/lib-dynload/_ctypes.so
#5 0x00000000004d91b6 in PyObject_Call ()
#6 0x000000000054c0da in PyEval_EvalFrameEx ()
#7 0x000000000054c272 in PyEval_EvalFrameEx ()
#8 0x0000000000575d92 in PyEval_EvalCodeEx ()
#9 0x00000000004c1352 in PyRun_SimpleFileExFlags ()
#10 0x00000000004c754f in Py_Main ()
#11 0x00007ffff68cb76d in __libc_start_main (main=0x41ba10 <main>, argc=2, ubp_av=0x7fffffffe1d8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe1c8)
at libc-start.c:226
#12 0x000000000041ba41 in _start ()
【问题讨论】:
你能把它分解成一个更小的MCVE,它不会做一堆不相关的事情,不需要scipy
等吗?这将使调试变得更加容易。
另外,当我运行它时,我得到的每个其他数字都类似于26815615859885194199148049996411692254958731641184786755447122887443528060147093953603748596333806855380063716372972101707507765623893139892867298012168192.000000
。你确定 C 代码是正确的吗?
我也注意到了这一点,但如果我在这两种情况下都将x[10][4]
更改为x[10][3]
,它又开始工作了。不知道那里发生了什么。
ct.cast(varsd, returnStruct_t)
几乎肯定会出现段错误。这就像在做returnStruct_t = *(returnStruct_t *)(&varsd)
。你可能想要ct.cast(varsd, returnStructPointer)
?
好吧,如果您的 C 代码不正确并且可能打印出未初始化的内存或其他内容,那么从 ctypes
segfaults 调用它也就不足为奇了。您是否尝试过从普通的旧 C 驱动程序调用相同的东西?
【参考方案1】:
您在这里至少有四个问题(实际上是五个,但一个无关紧要)。
(通常)导致您的段错误的行是这样的:
varsdb = ct.cast(varsd, returnStruct_t)
这是因为您试图将 void *
转换为 returnStruct_t
,而不是 returnStruct_t *
。由于returnStruct_t
比指针大得多,因此很有可能会在分配页面的末尾运行。即使它没有段错误,它也会给你垃圾值。相当于这个C代码:
returnStruct_t varsdb = *(returnStruct_t *)(&varsd);
你想要的是相当于:
returnStruct_t *varsdb = (returnStruct_t *)(varsd);
换句话说:
varsdb = ct.cast(varsd, returnStructPointer)
修复该问题后,我经常(但并非总是)在尝试访问 varsdb.contents.vars[0].x[0][0]
时仍然会遇到段错误(varsdb.contents.vars[0].x[0]
本身总是安全的)。
下一个问题是你没有正确定义你的结构。这是C:
typedef struct testStruct
double *x[11];
double *u[10];
Test;
这是 Python:
class vars_t(ct.Structure):
_fields_ = [("u", ct.POINTER(ct.c_double*10)),
("x", ct.POINTER(ct.c_double*11))]
您混淆了u
和x
。所以,你所称的x
,并被视为一个包含 11 个双精度数的数组,实际上是 u
,一个由 10 个双精度数组成的数组。所以每次你触摸 x[10] 时,你都会越过数组的末尾。
在修复那个之后,我打印出垃圾值。使用clang
构建,它总是接近6.92987533417e-310
。
我认为这是纯粹的 C 代码。我经常从 C 中的 x[10][4]
和 u[9][2]
行中打印出垃圾编号。同样,在相同的构建下,我得到了大约相等的合理值组合,两者都像 26815615859885194199148049996411692254958731641184786755447122887443528060147093953603748596333806855380063716372972101707507765623893139892867298012168192.000000
这样的数字,以及前者但nan
后者。
当我在valgrind
下运行一个简单的 C 驱动程序时,每隔四个fprintf
我就会得到这个:
==85323== Use of uninitialised value of size 8
因此,您可能在 C 中的分配或初始化代码中遇到了一个错误,您有时但并非总是能侥幸逃脱。
另外,这些不是同一类型:
typedef struct returnStruct_t
Test* vars;
ReturnStruct;
class returnStruct_t(ct.Structure):
_fields_ = [("vars", vars_t*numOpt)]
前者是一个长指针,该指针指向Test
对象的数组。后者是 3 个Test
对象。所以,再一次,你试图读取一个指向某个东西的指针作为该类型的值,而这里你已经超出了分配值的末尾。
修复该问题后,我不再遇到任何崩溃,并且即使在途中得到垃圾打印输出,我也会得到合理的最终值,例如 80.0
。但是当然,我仍然会得到那些垃圾打印输出,而 valgrind 仍在抱怨,所以显然这仍然不是最后一个问题。
您的代码中也有明显的泄漏——这与问题没有直接关系,但这是一个好兆头,表明您可能在其他地方遇到了类似的错误。您像这样分配x
数组:
for(x = 0; x < 11; x++)
varsStruct[i].x[x] = (double *)malloc(sizeof(double)*5);
...然后像这样释放它们:
for(x = 1; x < 11; x++)
free(returnStruct->vars[i].x[x]);
所以x[0]
永远不会被释放。
【讨论】:
谢谢,这非常有帮助!当我释放数组时,很好地抓住了错误的索引。我还意识到我的结构成员出现故障并修复了它们。除非我超出指定范围,否则我不会再收到任何段错误或垃圾编号。但是,当我尝试在将它们转换为varsdb
后打印出任何值时,我仍然没有得到任何我期望的数字。我想我会继续玩它的。感谢您让我走上正轨!以上是关于使用 ctypes 传递结构指针的主要内容,如果未能解决你的问题,请参考以下文章
没有为指向结构的指针调用 ctypes.Structure 子类的 ctypes __del__
在 Python/ctypes 中的结构内取消引用 C 函数指针