ctypes:例如,正确地将c_void_p子类化为传递和返回自定义数据类型

时间:2013-12-27 18:45:26

标签: python ctypes

我正在使用ctypes,似乎无法弄清楚如何使用自定义数据类型。希望有一个Python接口,用于C ++ cell类和C ++ cellComplex类的公共方法。

我目前的问题是使用名为get_elementsAtSpecifiedDim()的C函数,该函数在extern "C" {...下定义。编写此函数是为了返回一个void *,它是真正一个std:vector< cell<double>* >,一个指向C ++ cell对象的指针向量。

下面的我的客户端代码显示了对此函数的调用示例(通过Python CellComplex方法elemen()),这在我看来工作正常。您会注意到我的elemen()的Python实现将返回类型lib.elemen.restype声明为(Python)Cell对象的数组。这就是我得到的,>>>print cmplx.elemen()产生

[<cellComplex_python.Cell_Array_8 object at 0x10d5f59e0>, <cellComplex_python.Cell_Array_12 object at 0x10d5f5830>, <cellComplex_python.Cell_Array_6 object at 0x10d5f58c0>, <cellComplex_python.Cell_Array_1 object at 0x10d5f5950>]

但问题出在这里:

现在我想在此列表中的数组中的Cell个对象之一上调用我的C函数。例如,cmplx.elemen()[0][0]<Cell object at 0x10d5f5b00>,所以在我看来我应该能够这样做:

cmplx.elemen()[0][0].dim()但这段错误

我怀疑我没有正确创建自定义Python类CellCellComplex。特别是在Python类Cell中,方法dim(self):我有一行lib.dim.argtypes = [Cell],它必须绝对是假的。同样,我有这个愚蠢的Python类c_cellComplex除了允许我向自己指出一个特定的ctypes.c_void_p应该指向什么之外什么都不做。事实上,我声称我对这些Python类的定义完全是假的,我被欺骗认为我在正确的轨道上运行它的奇迹(直到我尝试调用Cell方法在假定的Cell实例上......

客户代码:

p = [[0,1],[0,1]] 
cmplx = cellComplex(p)
e = cmplx.elemen()

e[0][0].dim() # segfault

开始编辑 eryksun's answer below提供了如何子类化c_void_p的示例,并解决了一些其他概念问题 - 如果您遇到同样的问题,请从那里开始。

段错误问题来自get_elementsAtSpecifiedDim()中定义的extern C { ...将内存地址返回到std::vector<cell<double>* >这一事实,这是一种无法在Python中解析的数据类型。在这种情况下,我可以抓住向量中的指针并返回它们,如下所示:

extern "C" {

  void * get_elementAtSpecifiedDimAndLoc(void *ptr, int dim, int nr) {
    cellComplex<double>* cmplx = static_cast<cellComplex<double>* >(ptr);
    cell<double>* c = cmplx->elemen()[dim][nr];
    return c;
  }
}

可以像这样调用:

def elemen(self):    
     el = []
     for i in range(self.dim):
          size  = lib.size_elementsAtSpecifiedDim(self,i)               
          cells = []
          for j in range(size):
               cells.append(lib.get_elementAtSpecifiedDimAndLoc(self,i,j))            
          el.append(cells)
     return el
# put this somewhere
lib.get_elementAtSpecifiedDimAndLoc.restype  = Cell 
lib.get_elementAtSpecifiedDimAndLoc.argtypes = [CellComplex,c_int,c_int]

客户端代码现在有效。

结束编辑

这是华丽的愚蠢:

# cellComplex_python.py 
lib = ctypes.cdll.LoadLibrary('./cellComplex_lib.so')

class c_cellComplex(ctypes.c_void_p):
     pass

class Cell(ctypes.c_void_p):

     def dim(self):
          lib.dim.restype  = ctypes.c_int
          lib.dim.argtypes = [Cell]
          self.dimension = lib.dim(self)
          return self.dimension

class CellComplex(ctypes.c_void_p):

     def __init__(self,p):   
          self.dimension = len(p)
          lib.new_cellComplex.restype  = c_cellComplex
          lib.new_cellComplex.argtypes = [(ctypes.c_double*2)*self.dimension, 
                                           ctypes.c_size_t]
          e = [(ctypes.c_double*2)(p[i][0],p[i][1]) for i in range(self.dimension)]
          point = ((ctypes.c_double*2)*self.dimension)(*e)
          self.cmplx = lib.new_cellComplex(point,self.dimension)


     def elemen(self):
          lib.size_elementsAtSpecifiedDim.restype  = ctypes.c_int
          lib.size_elementsAtSpecifiedDim.argtypes = [c_cellComplex,
                                                      ctypes.c_int]
          lib.get_elementsAtSpecifiedDim.argtypes  = [c_cellComplex,ctypes.c_int]

          self.sizeAtDim = []
          self.elements  = []
          for i in range(self.dimension+1):

               self.sizeAtDim.append(lib.size_elementsAtSpecifiedDim(self.cmplx,i))
               lib.get_elementsAtSpecifiedDim.restype = Cell*self.sizeAtDim[i]
               self.elements.append(lib.get_elementsAtSpecifiedDim(self.cmplx,i))

          return self.elements

C代码:

// cellComplex_extern.cpp
#include<"cell.hpp">
#include<"cellComplex.hpp"> 

extern "C" {

  void * new_cellComplex(double p[][2], size_t dim) {
    std::vector< std::pair<double,double> > point;
    for (size_t i=0; i<dim; ++i) {
      point.push_back( std::make_pair(p[i][0],p[i][1]));
    }   
    cellComplex<double>* cmplx = new cellComplex<double>(point);
    return cmplx;
  }

  void * get_elementsAtSpecifiedDim(void *ptr, int dim) {
    cellComplex<double>* cmplx = static_cast<cellComplex<double>* >(ptr);
    std::vector<std::vector<cell<double>* > >* e = &cmplx->elemen();
    return &e[dim];
  }

  int size_elementsAtSpecifiedDim(void *ptr, int dim) {
    cellComplex<double>* cmplx = static_cast<cellComplex<double>* >(ptr);
    return cmplx->elemen()[dim].size();
  }  

  int dim(void *ptr) {
    cell<double>* ref = static_cast<cell<double>* >(ptr);
    return ref->dim();
  }

}

1 个答案:

答案 0 :(得分:1)

您可以定义类方法c_void_p和实例属性from_param,而不是子类化_as_parameter_。如果您只是代理在_obj等私有属性中引用的C ++对象,则可能不需要任何一个选项。也就是说,c_void_p的子类可以直接与ctypes指针,数组和结构一起使用,这在您的整体设计中可能很方便。

以下示例可能有所帮助:

from ctypes import *

__all__ = ['CellComplex']

class Cell(c_void_p):

    def __new__(cls, *args, **kwds):
        raise TypeError("cannot create %r instances" % cls.__name__)

    @property
    def dimension(self):
        return lib.dim(self)

class CellComplex(c_void_p):

    def __init__(self, p):
        pair = c_double * 2
        point = (pair * len(p))(*(pair(*q[:2]) for q in p))
        self.value = lib.new_cellComplex(point, len(p)).value

    @property
    def dimension(self):
        """Wrap a function that returns size_t."""
        return lib.????????(self)

    def get_elements(self):
        el = []
        for i in range(self.dimension):
            size = lib.size_elementsAtSpecifiedDim(self, i)
            cells = lib.get_elementsAtSpecifiedDim(self, i)
            el.append(cells[:size])
        return el

函数指针定义:

lib = CDLL('./cellComplex_lib.so')

lib.dim.restype = c_int
lib.dim.argtypes = [Cell]

lib.new_cellComplex.restype = CellComplex
lib.new_cellComplex.argtypes = [POINTER(c_double * 2), c_size_t]

lib.size_elementsAtSpecifiedDim.restype = c_int
lib.size_elementsAtSpecifiedDim.argtypes = [CellComplex, c_int]

lib.get_elementsAtSpecifiedDim.restype = POINTER(Cell)
lib.get_elementsAtSpecifiedDim.argtypes = [CellComplex, c_int]

我从类方法定义中分离出函数指针定义。每次调用方法时都无需重新定义函数指针restypeargtypes。如果函数返回一个可变大小的数组,最好将其设置为指针类型。您可以将结果切片到列表或cast到数组类型。

CellComplexp的浮点对序列[[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]初始化。

我删除了c_cellComplex课程。您只需设置实例的value即可。

lib.new_cellComplex会返回CellComplex的实例,但当__new__用作__init__时,ctypes会绕过CellComplexrestype,因此这不是问题。相反,覆盖__new__会更少扭曲,但您仍然必须覆盖c_void_p.__init__

dimension属性需要是一个调用导出函数的属性,而不是依赖于Python对象中的静态数据。