pybind / pybind11

Seamless operability between C++11 and Python
https://pybind11.readthedocs.io/
Other
15.84k stars 2.12k forks source link

cannot exchange objects from python <-> C++. The object is from a third library #4358

Open saroj31 opened 2 years ago

saroj31 commented 2 years ago

Discussed in https://github.com/pybind/pybind11/discussions/4357

Originally posted by **saroj31** November 23, 2022 I have a very basic question. I say basic because I am new to pybind11 and this is my first attempt to use the library to make python bindings for my C++ code. So here I am using a C++ class which internally uses a Realsense class. When I try to contruct a constructor for it then it gives me a TypeError. Can someone explain me what I am doing wrong in PYbind11 persepctive? Definitely I can see that there is a type mismatch here but Still I beleive this is a general problem which many of you would have faced. I want to solve this puzzle. Read all documentation but the cutom type caster example also uses an internal pybnd11 template internally. I don't think mine is a similar thing. I want to somhow pass the info from C++ side to python side and vice versa. If you can direct me to some right examples or documentation then it will be awesome. My C++ Class in Devicemanager.h/cpp : ``` include class DeviceManager { public: explicit DeviceManager(rs2::context& ctx); td::size_t getNumOfConnectedDevices(); } ``` My Binder.cpp code ``` #include #include "rsWrap/DeviceManager.h" #include #include #define STRINGIFY(x) #x #define MACRO_STRINGIFY(x) STRINGIFY(x) int add(int i, int j) { return i + j; } namespace py = pybind11; using namespace RealSenseWraLib; PYBIND11_MODULE(qco_py_wrapper, m) { py::class_(m, "DeviceManager") .def(py::init()) .def("getNumOfConnectedDevices", &DeviceManager::getNumOfConnectedDevices); m.def("add", &add, R"pbdoc( Add two numbers Some other explanation about the add function. )pbdoc"); } ``` The python Code that I want to make work is: ``` import pyrealsense2 as rs import qco_py_wrapper as qco dmanager = qco.DeviceManager(rs.context()); print(dmanager) ``` Error That I get is this: ``` Traceback (most recent call last): File " **/build/python_wrapper/test.py", line 5, in dmanager = qco.DeviceManager(rs.context()); TypeError: __init__(): incompatible constructor arguments. The following argument types are supported: 1. qco_py_wrapper.DeviceManager(arg0: rs2::context) Invoked with: ``` When I do a help on qco_py_wrapper then the following appears: ``` Help(qco_py_wrapper): `Help on module qco_py_wrapper: NAME qco_py_wrapper CLASSES pybind11_builtins.pybind11_object(builtins.object) DeviceManager class DeviceManager(pybind11_builtins.pybind11_object) | Method resolution order: | DeviceManager | pybind11_builtins.pybind11_object | builtins.object | | Methods defined here: | | __init__(...) | __init__(self: qco_py_wrapper.DeviceManager, arg0: rs2::context) -> None | | getNumOfConnectedDevices(...) | getNumOfConnectedDevices(self: qco_py_wrapper.DeviceManager) -> int | | ---------------------------------------------------------------------- | Static methods inherited from pybind11_builtins.pybind11_object: | | __new__(*args, **kwargs) from pybind11_builtins.pybind11_type | Create and return a new object. See help(type) for accurate signature. ``` `
Skylion007 commented 2 years ago

You need to have a definition of py::class() already defined so the caster knows what to do with the python version of rs2::context (ie. convert it into a pybind11 class).

saroj31 commented 2 years ago

Thank @Skylion007 , OK I will try to do that.

Just to verify my understanding and to clarify. Here rs2::context is a C++ class and pyrealsense2.pyrealsense2.context is a python realsense context class.

Here do I have to write a my own pyclass_ class to convert from Python object to my C++ class ? Sorry if question appears basic but I am trying my first hand with pybind11 and I am not able to understand which part of the documentation explains this behavior.

Skylion007 commented 2 years ago
 py::class_<rs2::context>(m, "rs2_context");
saroj31 commented 2 years ago

I did the same.

also in another attempt I added a default constructor in it like this: py::class_ < rs2::context >(m, "rs2_context") .def(py::init());

but in both the cases I got the below error. I get this error now:

Traceback (most recent call last): File "~/build/python_wrapper/test.py", line 5, in dmanager = qco.DeviceManager(rs.context()); TypeError: init(): incompatible constructor arguments. The following argument types are supported:

  1. qco_py_wrapper.DeviceManager(arg0: qco_py_wrapper.context)

Invoked with: <pyrealsense2.pyrealsense2.context object at 0x7fe65df68230>

saroj31 commented 2 years ago

So, Finally I wrote a custom type caster after that it worked. Now I can work around with this topic I think.

namespace PYBIND11_NAMESPACE { namespace detail {
        template <> struct type_caster<rs2::context> {
        public:
            /**
             * This macro establishes the name 'rs2::context' in
             * function signatures and declares a local variable
             * 'value' of type rs2::context
             */
        PYBIND11_TYPE_CASTER(rs2::context, const_name("rs2::context"));

            /**
             * Conversion part 1 (Python->C++): convert a PyObject into a rs2::context.
             *The second argument
             * indicates whether implicit conversions should be applied.
             */
            bool load(handle src, bool) {

                std::cout<<"python to c++ conversion"<<std::endl; //debug code

                rs2::context context;
                value = context;

                return true;
            }

            /**
             * Conversion part 2 (C++ -> Python): convert an rs2::context instance into
             * a Python object. The second and third arguments are used to
             * indicate the return value policy and parent object (for
             * ``return_value_policy::reference_internal``) and are generally
             * ignored by implicit casters.
             */
            static handle cast(rs2::context src, return_value_policy /* policy */, handle /* parent */) {

                std::cout<<"C++ to python conversion"<<std::endl; //debug mode
                return py::bool_(true).release();
            }
        };
    }} // namespace PYBIND11_NAMESPACE::detail

This has made it work on my side. Here I am not understanding how it works. Just debugging it using some std::cout made me navigate the problem and understand where to put my code. Feel free to correct the code here if something needs to be changed. It would be great for understanding the mechanism and help me solve my problems in future.

saroj31 commented 2 years ago

Can I convert a PyObject object to a C++ object type ? I try to do it this way:

                bool load(handle src, bool) {
                     PyObject *py_object_src = src.ptr();
                     rs2::context *tmp = reinterpret_cast<rs2::context *>(py_object_src);

I use this code in the load() of the type_caster. After that I get a segmentation fault in my Python test.py. Looks like wherever I try to use that tmp object in my C++ side it gives a seg fault. It would be best If I can access the C++ object from the PyObject that I get. I can assign that to my C++ Context object.

MY Goal is to access the context object that python has sent from python to make a similar or a copy in C++ side. Any help is welcome here.

I am ok to hear that,"pybind11 is not used for this purpose, and you are using it in a wrong way bro" too.

virtuald commented 1 year ago
CppObject *tmp = py_object.cast<CppObject*>();