简体   繁体   English

Cython:如何包装返回C ++对象的C ++函数?

[英]Cython: How to wrap a C++ function that returns a C++ object?

I'm working on a Python project where I'd like to interface with a C++ package that has already been written. 我正在一个Python项目中,我想与已经编写的C ++包进行交互。 Since I'll be using Cython in other portions of this project, I'd prefer to wrap using Cython. 由于我将在该项目的其他部分中使用Cython,因此我更喜欢使用Cython进行包装。

In brief, I need to wrap a function, FooBar, that returns an object of custom class type Bar. 简而言之,我需要包装一个函数FooBar,该函数返回自定义类类型Bar的对象。

Here's the Bar.h: 这是Bar.h:

#include <cstddef> // For size_t
#include <vector>

/* data returned by function FooBar()*/
class Bar {
public:
    size_t X;
    std::vector<size_t> Y;      
    std::vector<double> Z;  
    std::vector<double> M;  
    std::vector<size_t> N;  

};


Bar FooBar(const std::vector<double> & O, size_t P, size_t Q);

And PyBar.pyx: 和PyBar.pyx:

from libcpp.vector cimport vector

cdef extern from "Bar.h":
    cdef cppclass Bar:
        size_t X
        vector[size_t] Y    
        vector[double] Z    
        vector[double] M 
        vector[size_t] N    
    cdef Bar FooBar(const vector[double] & O, size_t P, size_t Q)

cdef class PyBar:
    cdef Bar *thisptr      # hold a C++ instance which we're wrapping

    def __cinit__(self, O, P, Q):
        C_Bar = FooBar(O, P, Q)
        self.thisptr = &C_Bar

    def __dealloc__(self):
        del self.thisptr

Actual Question: Is this even the right approach to what I want to do? 实际的问题:这甚至是我想要做的正确方法吗? For reference, if I just tried to wrap the class by itself I have had no problem: I can import the module, create objects using PyBar(), and underlying C methods implemented on the class would work. 作为参考,如果我只是尝试自己包装类,那么我没有问题:可以导入模块,使用PyBar()创建对象,并且在类上实现的基础C方法将起作用。 The issue is trying to wrap a function that returns objects of the C++ class. 问题是试图包装一个返回C ++类对象的函数。 In the wild, I'll never actually want to create PyBar representation of any Bar object that wasn't created by FooBar, so this is the approach I decided upon after much head scratching. 在野外,我永远不会真正想为任何不是由FooBar创建的Bar对象创建PyBar表示形式,因此这是我在大量抓挠之后决定采用的方法。

With respect to the first part of the problem, I think the more elegant change would be to have FooBar defined as: 关于问题的第一部分,我认为更优雅的更改是将FooBar定义为:

Bar* FooBar(const std::vector<double> & O, size_t P, size_t Q);

and have it return a "new" allocated pointer. 并返回“分配的”新指针。 I think in your original Cython code __cinit__ you'll create a stack allocated Bar, take a pointer of that, and then that will expire resulting in eventual disaster. 我认为在您最初的Cython代码__cinit__您将创建一个分配了Bar的堆栈,获取该Bar的指针,然后该指针将过期,最终导致灾难。

An alternative solution that might work would be be to keep FooBar returning Bar, change PyBar so it starts 一种可行的替代解决方案是保持FooBar返回Bar,更改PyBar以使其开始

cdef class PyBar:
   cdef Bar this_obj

   def __cinit__(self, O, P, Q):
      self.this_obj = FooBar(O,P,Q)

ie keeps an object rather than a pointer. 即保留一个对象而不是一个指针。 No __dealloc__ should be necessary. 不需要__dealloc__

I don't know about the undefined symbol error... 我不知道未定义的符号错误...

After playing with this for awhile, the only semi-elegant (emphasis on the semi) solutions I found do involve modifying the existing C++ code. 在玩了一段时间之后,我发现唯一的半优雅(强调半)的解决方案确实涉及到修改现有的C ++代码。 The approach I half-implemented in my question has many problems and should probably be ignored. 我在问题中半执行的方法有很多问题,可能应该忽略。

Maybe someone with more experience writing C++ code can come up with something better, but for the sake of posterity: 也许有更多编写C ++代码经验的人可以提出更好的建议,但是为了后代:

I personally found it easier to modify FooBar() so that it is a member function of Bar: instead of returning a Bar object, it now modifies the instance it is called from. 我个人发现修改FooBar()使其更容易成为Bar的成员函数:现在,它不再修改Bar对象,而是修改了从其调用的实例。 Then, when wrapping Bar in Cython, I do not expose FooBar() as a class method, but I do call the method in the constructor for the Python (and thus, the corresponding C++) object. 然后,将Bar包装在Cython中时,我没有将FooBar()公开为类方法,而是在Python(以及相应的C ++)对象的构造函数中调用了该方法。 This works for me because, as I stated, I really only ever intend to deal with Bar objects that have been initialized with some set of values by FooBar(). 这对我有用,因为正如我所说,我实际上只打算处理由FooBar()用某些值初始化的Bar对象。

In the end, I chose this approach over using a copy constructor (which would allow me to initialize a new Python/corresponding C++ Bar object from an existing Bar object created by FooBar), because it seemed more readable to me. 最后,我选择了这种方法,而不是使用复制构造函数(这使我可以从FooBar创建的现有Bar对象中初始化一个新的Python /对应的C ++ Bar对象),因为它对我而言似乎更具可读性。 The advantage of the copy constructor approach would be that one would only have to modify the Bar class definition in C (adding a copy constructor), which might be preferable if you're truly uncomfortable about changing the implementation of FooBar(). 复制构造函数方法的优点是,只需修改C中的Bar类定义(添加复制构造函数),如果您真的不满意更改FooBar()的实现,则可能更可取。 In my case, since Bar objects can sometimes contain very large vectors, a copy constructor also seemed like a bad idea for performance reasons. 就我而言,由于Bar对象有时可以包含很大的向量,因此出于性能原因,复制构造函数似乎也不是一个好主意。

Here's my final code: 这是我的最终代码:

Bar.h: Bar.h:

#include <cstddef> // For size_t
#include <vector>

class Bar {
public:
    size_t X;
    std::vector<size_t> Y;
    std::vector<double> Z;
    std::vector<double> M;
    std::vector<size_t> N;

    void FooBar(const std::vector<double> & O, size_t P, size_t Q);

    ClusterResult(){}


};

PyBar.pyx: PyBar.pyx:

from libcpp.vector cimport vector

cdef extern from "Bar.h":
    cdef cppclass Bar:
        size_t X
        vector[size_t] Y    
        vector[double] Z    
        vector[double] M  
        vector[size_t] N
        Bar()    
        void FooBar(const vector[double] & O, size_t P, size_t Q)


cdef class PyBar:
    cdef Bar *thisptr      # hold a C++ instance which we're wrapping

    def __cinit__(self, O, P, Q):
        self.thisptr = new Bar()
        self.thisptr.FooBar(O, P, Q)

    def __dealloc__(self):
        del self.thisptr


#Below, I implement the public attributes as get/setable properties.
#could have written get/set functions, but this seems more Pythonic.

    property X:
        def __get__(self): return self.thisptr.X
        def __set__(self, X): self.thisptr.X = X

    property Y:
        def __get__(self): return self.thisptr.Y
        def __set__(self, Y): self.thisptr.Y = Y

    property Z:
        def __get__(self): return self.thisptr.Z
        def __set__(self, Z): self.thisptr.centers = Z

    property M:
        def __get__(self): return self.thisptr.M
        def __set__(self, size): self.thisptr.M = M

    property N:
        def __get__(self): return self.thisptr.N
        def __set__(self, size): self.thisptr.N = N

Refactoring FooBar() Implementation: 重构FooBar()实现:

Then, I rewrote the implementation of FooBar() in Bar.cpp, changing the return type to void and substituting the Bar result object previously returned by the function for this . 然后,我重写FooBar的()在Bar.cpp的实施,改变了返回类型无效,置换Bar result以前由该函数返回的对象this For example (and being explicit in my use of this for the sake of clarity): 例如(为了清楚起见,在我的使用中明确指出):

Bar FooBar(const std::vector<double> & O, size_t P, size_t Q)
{

    Bar result = new Bar();
    result.X = P + 1;
    result.Z = std::sort(O.begin()+1, O.end());
    const size_t newParam = Q + 2;

    someOtherFunction(newParam, result);

    ...

}

would become something like this: 会变成这样:

void Bar::FooBar(const std::vector<double> & O, size_t P, size_t Q)
{

    this->X = P + 1;
    this->Z = std::sort(O.begin()+1, O.end());
    const size_t newParam = Q + 2;

    someOtherFunction(newParam, *this);

...

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM