meteoritepanama
meteoritepanama

Reputation: 6252

Passing C++ pointer as argument into Cython function

cdef extern from "Foo.h":
    cdef cppclass Bar:
        pass

cdef class PyClass:
    cdef Bar *bar

    def __cinit__(self, Bar *b)
        bar = b

This will always give me something like:
Cannot convert Python object argument to type 'Bar *'

Is there a way to accomplish this, or do I need to extract everything from a Bar object, create a Python equivalent, pass it in, then reconstruct it in PyClass?

Upvotes: 23

Views: 7345

Answers (4)

truhanen
truhanen

Reputation: 663

As of Cython 0.21 it has been possible to declare cdef methods with the @staticmethod decorator. This allows static creator methods that take non-Python arguments:

cdef extern from "Foo.h":
    cdef cppclass Bar:
        pass

cdef class PyClass:
    cdef Bar *bar

    @staticmethod
    cdef create(Bar *bar):
        cdef PyClass pc = PyClass()
        pc.bar = bar
        return pc

Upvotes: 8

Andrew
Andrew

Reputation: 3061

I came across this problem trying to wrap C code with structs as python classes. The issue seems to be that "special" function including __init__ and __cinit__ must be declared as def rather than cdef. This means that they can be called from normal python, so the type parameters are effectively ignored and everything is treated as object.

In J.F. Sebastian's answer the fix is not the wrapping - a double is a basic numeric type and so there is a default conversion between the C/C++ type and the Python object. Czarek's answer is basically correct - you need to use a fake constructor idiom, using a global function. It is not possible to use a @staticmethod decorator as they cannot be applied to cdef functions. The answer looks simpler on the original example provided.

cdef extern from "Foo.h":
    cdef cppclass Bar:
        pass

cdef class PyClass:
    cdef Bar *bar

cdef PyClass_Init(Bar *b):
    result = PyClass()
    result.bar = b
    return result

Upvotes: 10

Czarek Tomczak
Czarek Tomczak

Reputation: 20687

For each cdef class create a global cdef function that acts as a constructor, CefResponse is a C++ object, PyResponse a python equivalent of a c++ object:

cdef object CreatePyResponse(CefRefPtr[CefResponse] cefResponse):

    pyResponse = PyResponse()
    pyResponse.cefResponse = cefResponse
    return pyResponse

cdef class PyResponse:

    cdef CefRefPtr[CefResponse] cefResponse

    def GetStatus(self):

        return (<CefResponse*>(self.cefResponse.get())).GetStatus()

So instead of resp = PyResponse(cppObject) call resp = CreatePyResponse(cppObject).

Example taken from CEF Python: https://code.google.com/p/cefpython/source/browse/cefpython/response.pyx?r=0250b65e046a

Upvotes: 5

jfs
jfs

Reputation: 414875

Python class accepts Python arguments. To pass a C++ argument you need to wrap it:

# distutils: language = c++

cdef extern from "Foo.h" namespace "baz":
    cdef cppclass Bar:
         Bar(double d)
         double get()

cdef class PyBar: # wrap Bar class
    cdef Bar *thisptr
    def __cinit__(self, double d):
        self.thisptr = new Bar(d)
    def __dealloc__(self):
        del self.thisptr
    property d:
        def __get__(self):
            return self.thisptr.get()

PyBar instances can be used as any other Python objects both from Cython and pure Python:

class PyClass:
    def __init__(self, PyBar bar):
        self.bar = bar

print(PyClass(PyBar(1)).bar.d)

Upvotes: 1

Related Questions