0

I have a simple test case for polymorphism that isn't working quite right.. I have two C++ classes, Client and Dispatcher. The dispatcher has a method called "Dispatch" that takes a Client* as an input and calls the clients virtual ProcessEvent() method. I've made both of these classes available to python using the code below. I've derived from the Client class as the python class SomeClient and overridden its ProcessEvent function. When I call "Dispatch" from anywhere EXCEPT inside the SomeClient:: ProcessEvent method, everything works as expected, I get a report from the child class. But when I call Dispatch from within SomeClient:: ProcessEvent, the base classes ProcessEvent is called. Any ideas what could be going wrong here? I expect to get an infinite recursion that results in SomeClient:: ProcessEvent being called forever.

C++:

#include <iostream>
#include "pybind11/pybind11.h"

namespace py = pybind11;
class Dispatcher;
class Client
{
public:
    Client(Dispatcher* disp): PtrD(disp)
    {
        std::cout << "In Client::Client\n";
    }
    virtual ~Client(){};
    virtual void ProcessEvent()
    {
        std::cout << "THIS SHOULDN'T HAPPEN --In Client::ProcessEvent\n";
    }
    Dispatcher* PtrD;
};

class Dispatcher
{
public:
    Dispatcher()
    {
        std::cout << "In Dispatcher::Dispatcher\n";
    }
    virtual ~Dispatcher(){};

    void Dispatch(Client* client)
    {
        std::cout << "Dispatcher::Dispatch called by " << client << std::endl;
        client->ProcessEvent();
    }
};

class DispatcherTrampoline : public Dispatcher
{
public:
    using Dispatcher::Dispatcher;
};

class ClientTrampoline : public Client
{
public:
    using Client::Client;

    void ProcessEvent() override
    {
        PYBIND11_OVERLOAD(void,Client,ProcessEvent,);
    }
};

PYBIND11_MODULE(libTestCase,mod)
{
    py::class_<Client,ClientTrampoline> cli(mod,"Client");
    cli.def(py::init<Dispatcher* >());
    cli.def("ProcessEvent",&Client::ProcessEvent);
    cli.def_readwrite("PtrD",&Client::PtrD);

    py::class_<Dispatcher,DispatcherTrampoline> dsp(mod,"Dispatcher");
    dsp.def(py::init< >());
    dsp.def("Dispatch",&Dispatcher::Dispatch);
}

Python Test:

from build.libTestCase import Client,Dispatcher

class SomeClient(Client):
    def __init__(self,d):
        print("In SomeClient::__init__")
        super().__init__(d);

    def ProcessEvent(self):
        print("in SomeClient::ProcessEvent,about to call self.ProcessEvent")
        self.PtrD.Dispatch(self);

if __name__ == "__main__":
    dd = Dispatcher()
    cl = SomeClient(dd)
    dd.Dispatch(cl)

TERMINAL OUTPUT:

In Dispatcher::Dispatcher
In SomeClient::__init__
In Client::Client
Dispatcher::Dispatch called by 0x20bb270
in SomeClient::ProcessEvent,about to call self.ProcessEvent
Dispatcher::Dispatch called by 0x20bb270
THIS SHOULDN'T HAPPEN --In Client::ProcessEvent
IAS_LLC
  • 135
  • 11
  • I really do need this solved...As such, I'm willing to PAY for the fix: https://www.upwork.com/job/Fix-bug-Simple-Python-PyBind11-Module_~01155fc34bd0078416/ – IAS_LLC Oct 05 '18 at 18:39
  • 1
    Sorry this is a bit vague, but I don't think what you're trying to do here is possible. When you pass a `SomeClient` object to `Dispatch` it will always cast it to a C++ object with a vtable it can get an offset from. The python override can't be resolved in this way. If you need a python callback passed to the dispatcher it might be easier to follow one of the examples [here](https://github.com/pybind/pybind11/blob/master/tests/test_callbacks.cpp) – ChrisD Oct 06 '18 at 01:26
  • But why does it work inside of __main__? Both calls originate from python? – IAS_LLC Oct 06 '18 at 13:33
  • In the python test script `cl` is a python object and its `ProcessEvent` is resolved by the interpreter. In `Dispatcher::Dispatch`, however, `client` is a C++ object and the address of its `ProcessEvent` is obtained from the vtable the compiler attaches to a `Client` and anything that derives from it. When you implement a new derived class in python, there is no mechanism to define a new vtable, and in any case no compiled function whose address could be inserted into it. But you *can* pass `cl.ProcessEvent` as a `py::object` which can then be called from within c++. – ChrisD Oct 06 '18 at 17:18
  • Apologies I misunderstood, your question is about why it calls the correct overload the first time but then doen't recurse. I agree *that* is very puzzling. (and also makes what I said above completely wrong). – ChrisD Oct 07 '18 at 02:14
  • I've opened up an issue on the Pybind11 github: https://github.com/pybind/pybind11/issues/1552 – IAS_LLC Oct 08 '18 at 11:59
  • fwiw I redid the above using boost::python and got the expected behaviour (recursion until max depth). – ChrisD Oct 08 '18 at 23:19
  • I ran into a similar issue, the reason was the Python object was gc'ed. I had to keep reference on the pybind11::object into my C++ trempolin. – Hulud Aug 04 '20 at 11:52

0 Answers0