2

I created a program that exposes class with virtual function to python using boost::python. Also I configured everything so that I could load python module that was created by boost::python and retrieve an instance of that object and it's child in python module. But I couldn't extract the python object with type Base or Alpha to Base * pointer.

I have the same error as here

Source code for main:

Py_Initialize();

    try {
        auto sys_module {bp::import("sys")};
        auto sys_path {sys_module.attr("path")};
        sys_path.attr("append")("bin");

        bp::import("py_lib");
        auto module_test {bp::import("module_test").attr("__dict__")};

        const char* ident       = bp::extract< const char* >( module_test["Ident"]() );
        const char* newgameplay = bp::extract< const char* >( module_test["NewGamePlay"]() );

        printf("Loading Script: %s\n", ident);

        auto py_interface_inst {module_test[newgameplay]()};

    const auto is_subclass {bp::is_subclass
        bp::extract<Base *> extractor {py_interface_inst};
        if (extractor.check()) {
           auto cpp = extractor();
           cpp->FunctionCall();
        }
    } catch (const boost::python::error_already_set&) {
        PyErr_Print();
    }
    return 0;

Module defenition code:

class Base
{
public:
    Base() {};
    virtual void FunctionCall() {std::cout << "Base";}
    virtual ~Base() {};
};

class BaseWrap : public Base, public bp::wrapper<Base>
{
public:
    void FunctionCall() override
    {
        if (bp::override f = this->get_override("FunctionCall"))
            f();
        else Base::FunctionCall();
    }
};

BOOST_PYTHON_MODULE(PY_MODULE_NAME)
{
    class_<BaseWrap, boost::noncopyable>("Base")
        .def("FunctionCall", &Base::FunctionCall, &BaseWrap::FunctionCall)
    ;

    class_<Hello>("Hello")
        ;
    class_<HelloDerived, bases<Base>>("HelloDerived")
        ;
}

and python code file

import sys
import py_lib

def Ident():
    return "Alpha"

def NewGamePlay():
    return "NewAlpha"


def NewAlpha():
    import py_lib 

    class Alpha(py_lib.Base):
        def __init__(self):
            super().__init__()
            print("Made new Alpha!")

        def FunctionCall(self):
            print("This is function test Alpha!")

    base : py_lib.Base = py_lib.Base()
    derived : py_lib.Base = Alpha()

    for el in [base, derived]:
        el.FunctionCall()

    return Alpha()

While debugging it seems that the extractor fails on python function

PyAPI_FUNC(int) PyType_IsSubtype(PyTypeObject *, PyTypeObject *);

This is in the function

// ..\boost_1.84.0\libs\python\src\object\class.cpp
 BOOST_PYTHON_DECL void*
 find_instance_impl(PyObject* inst, type_info type, bool null_shared_ptr_only)
 {
     if (!Py_TYPE(Py_TYPE(inst)) ||
             !PyType_IsSubtype(Py_TYPE(Py_TYPE(inst)), &class_metatype_object))
         return 0;

         instance<>* self = reinterpret_cast<instance<>*>(inst);
     ...

If i skip this check and go straight to the reinterpret_cast then everything is great. I'm able to call methods of Base and overriden methods of Alpha.

I use msvc143 for compiling, CMake for building and boost 1.84 for boost::python.

While executing Bases' construtor is called, so the base object is created for sure.

Can you help me to undertand what could be the problem? Thanks

0

1 Answer 1

0

I tried to do the same thing using pybind11. It worked perfectly. Couldn't make it work for boost for some reason. Frustrating

The setup is really basic.

Module code

PYBIND11_MODULE(PY_MODULE_NAME, m)
{
    py::class_<Hello, HelloDerived>(m, "HelloDerived")
        .def(py::init<>())
        .def("greet", &Hello::greet)
    ;
}

Class definitions. Hello as a base and HelloDerived as a trampoline class

struct Hello {
    Hello() {};
    virtual ~Hello() {};
    virtual void greet() { std::cout << "Kek" << std::endl; }
};
struct HelloDerived : public Hello {
void greet()  override {
        PYBIND11_OVERRIDE(
            void,
            Hello,
            greet
        );
    }
};

Then I build it using cmake in target directory.

project (py_lib)
add_library (${PROJECT_NAME} MODULE)
set_target_properties (${PROJECT_NAME} PROPERTIES SUFFIX ".pyd")

set (MAIN_OUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin)
set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${MAIN_OUT_PATH}/.build/cmake_artifacts/${CMAKE_BUILD_TYPE})
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${MAIN_OUT_PATH}) 
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${MAIN_OUT_PATH})

configure_file (config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h @ONLY)

target_sources (${PROJECT_NAME} PUBLIC 
    my_interface.cpp
    py_span_converter.h
)

if (CMAKE_VERSION VERSION_GREATER 3.12)
  set_property (TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
endif()

target_link_libraries (${PROJECT_NAME} PRIVATE
    ${Boost_LIBRARIES}
    pybind11::embed
)

Then I create python script where I load this module.

def NewAlpha():
    import py_lib 

    class Alpha(py_lib.HelloDerived):
        def __init__(self):
            super().__init__()
            print("Made new Alpha!")

        def greet(self):
            print("This is function test Alpha!")

    base : py_lib.HelloDerived = py_lib.HelloDerived()
    derived : py_lib.HelloDerived = Alpha()

    for el in [base, derived]:
        el.greet()

    return Alpha()

Then I load this code back into cpp program, where I include pybind created lib too. This is a code sample from my main function

auto main_py {py::scoped_interpreter()};
    try {

        fs::path program {boost::dll::program_location().native()};
        program = program.parent_path() ;
        auto sys_module {py::module::import("sys")};
        auto sys_path {sys_module.attr("path")};
        sys_path.attr("append")("bin");
        fs::path test_path {program / "bin" / "module_test.py"};

        auto module_test {py::module::import("module_test")};
        auto module_test_ns {module_test.attr("__dict__")};

        auto ident       { module_test_ns["Ident"]().cast<std::string>()};
        auto newgameplay { module_test_ns["NewGamePlay"]().cast<std::string>() };

        std::cout << "Loading Script " << ident << std::endl;
        auto py_interface_inst {module_test_ns[newgameplay.c_str()]()};
        std::string className {py::cast<std::string>(py_interface_inst.attr("__class__").attr("__name__"))};
        std::cout << className << std::endl;

        auto derived {py_interface_inst.cast<Hello *>()};
        derived->greet();
    } catch (const py::error_already_set& err) {
        std::cerr << "Python error:\n" << err.what() << std::endl;
    }

You can check full working example at https://github.com/Alegmito/cpp-python-link-test/tree/py_bind_dev

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.