使用 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 *)(&amp;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))]

您混淆了ux。所以,你所称的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 函数指针

从 Python (ctypes) 指向 C 以保存函数输出的指针

ctypes给扩展模块中的函数传递数组和结构体

ctypes 操作 python 与 c++ dll 互传结构体指针