2016-01-20 52 views
3

我有现成的C++代码来定义一些我需要使用的类,但我需要能够将这些类发送到Python代码。具体来说,我需要用C++创建类实例,创建Python对象来充当这些C++对象的包装,然后将这些Python对象传递给Python代码进行处理。这只是一个更大的C++程序中的一部分,所以最终需要使用C/Python API在C++中完成。Cython扩展类:如何在自动生成的C结构中公开方法?

为了让我的生活更轻松,我使用了Cython来定义扩展类(cdef类),它充当我的C++对象的Python包装器。我使用的典型格式是cdef类包含一个指向C++类的指针,该指针在创建cdef类实例时被初始化。由于我也希望能够替换指针,如果我有一个现有的C++对象来包装,我已经添加了方法到我的cdef类到C++对象并取其指针。我的其他cdef类在Cython中成功使用accept()方法,例如,当一个对象拥有另一个对象时。

下面是我用Cython代码示例:

MyCPlus.pxd

cdef extern from "MyCPlus.h" namespace "mynamespace": 
    cdef cppclass MyCPlus_Class: 
     MyCPlus_Class() except + 

PyModule.pyx

cimport MyCPlus 
from libcpp cimport bool 

cdef class Py_Class [object Py_Class, type PyType_Class]: 
    cdef MyCPlus.MyCPlus_Class* thisptr 
    cdef bool owned 

    cdef void accept(self, MyCPlus.MyCPlus_Class &indata): 
     if self.owned: 
      del self.thisptr 
     self.thisptr = &indata 
     self.owned = False 

    def __cinit__(self): 
     self.thisptr = new MyCPlus.MyCPlus_Class() 
     self.owned = True 

    def __dealloc__(self): 
     if self.owned: 
      del self.thisptr 

问题是当我尝试从C访问accept()方法++。我尝试在我的cdef类和accept()方法上使用publicapi关键字,但我无法弄清楚如何在Cython的自动生成的.h文件中的C结构中公开此方法。不管我怎么努力,在C结构是这样的:

PyModule.h(通过用Cython自动生成)

struct Py_Class { 
    PyObject_HEAD 
    struct __pyx_vtabstruct_11PyModule_Py_Class *__pyx_vtab; 
    mynamespace::MyCPlus_Class *thisptr; 
    bool owned; 
}; 

我也试着键入self输入为Py_Class,我甚至试过前置声明Py_Classpublicapi关键字。我也尝试使accept()成为一种静态方法。我已经尝试过的任何方法都可以公开accept()方法,以便我可以在C++中使用它。我尝试通过__pyx_vtab尝试访问它,但是我收到了一个编译器错误,“不完整类型的无效使用”。我搜索了很多,但没有看到解决方案。谁能帮我?谢谢,麻烦您了!

+0

'static void __pyx_f_8PyModule_8Py_Class_accept(struct __pyx_obj_8PyModule_Py_Class * __ pyx_v_self,mynamespace :: MyCPlus_Class&__ pyx_v_indata);/* proto * /'看起来似乎是可以使用的东西。 – DavidW

+0

@DavidW,这可能会工作一段时间,但我真的在寻找一种解决方案,告诉Cython将此方法放入我的API中。上面引用的行深埋在自动生成的'.cpp'文件中,并且可能会意外更改。我需要一个更可维护的解决方案。不过,谢谢你的尝试。 –

+0

你可以尝试的另一件事是在* pxd和pyx文件中声明'accept()' - 如果你把它作为一个完全声明的类方法(而不仅仅是一个'extern'方法原型被暴露),并且Cython生成一个相应命名的可访问方法,它会是你的调用或覆盖 – fish2000

回答

0

声明accept时,您可能要使用cpdef而不是cdef。见the docs

从Python和C
赎回*声明与cpdef声明。
*可以从任何地方调用,因为它使用了一点Cython魔法。
*从其他Cython代码调用时使用更快的C调用约定。

试试看!

+0

我不需要从Python访问'accept()';仅从Cython和C++(我确实已经在Cython中验证了它的作用)。我确实试过这个,但是Cython提供了一个错误,因为'MyCPlus.MyCPlus_Class'不是一个Python对象。谢谢你的尝试。 –

+0

哦,哈哈我的错误 - 只是好奇,如果你没有明确的类型声明再次尝试,你会得到什么?我觉得'pyx'文件中的类型注释信息应该是可选的,并且被视为“暗示”,但我可能完全误解了这一点。 – fish2000

+1

对于Python可访问的函数,我在'pyx'文件中使用静态类型来强制Python对输入执行自动转换(它也提供了一定级别的类型检查)。对于C可访问的函数,Cython需要知道如何声明函数,因此可以使用静态类型来指定。如果你没有指定,我认为它假定某物是一个Python对象。如果您指定了一个Cython扩展类,那么您还需要声明该类为'public'或'api',否则编译器将不知道它是什么。这就是为什么我试图将这些关键字放在类和方法上的原因。 –

1

这不是一个真正的解决方案,但我想出了一个解决方案来解决我的问题。我仍然希望有一个解决方案,让我告诉Cython将accept()方法暴露给C++。

我的解决方法是我为我的Python类(不是方法)编写了一个单独的函数。然后我给api关键字都与我的Python类和新功能:

cdef api class Py_Class [object Py_Class, type PyType_Class]: 
(etc.) 

cdef api Py_Class wrap_MyCPlusClass(MyCPlus.MyCPlus_Class &indata): 
    wrapper = Py_Class() 
    del wrapper.thisptr 
    wrapper.thisptr = &indata 
    wrapper.owned = False 
    return wrapper 

这得到不同类别我需要包装的数量有点笨拙,但至少用Cython回放功能的API中它易于使用:

struct Py_Class* wrap_MyCPlusClass(mynamespace::MyCPlusClass &); 
2

,正如你在comment指出,它似乎是__pyx_vtab成员是用Cython只使用,因为它甚至没有在出口头定义结构类型为它(S)。

添加到您的回复,一种方法也可以是:

cdef api class Py_Class [object Py_Class, type Py_ClassType]: 
    ... 
    cdef void accept(self, MyCPlus.MyCPlus_Class &indata): 
     ... # do stuff here 
    ... 


cdef api void (*Py_Class_accept)(Py_Class self, MyCPlus.MyCPlus_Class &indata) 
Py_Class_accept = &Py_Class.accept 

基本上,我们定义一个函数指针,并将其设置为我们要公开的扩展的方法。这与您的回复的cdef'd函数没有多大区别;主要区别在于我们可以像往常一样在类定义中定义我们的方法,而不必将功能或方法/函数调用复制到另一个函数来暴露它。需要注意的是,除了self的扩展类型(在本例中)等外,我们必须几乎逐字定义函数指针的签名。那么这也适用于常规功能。

请注意,我在一个C级的Cython .pyx文件上试过这个,我没有也不打算在CPP实现文件上测试它。但是,我想,希望这可能工作得很好。