How to send signal using callback called from external script?

ぐ巨炮叔叔 提交于 2019-12-08 05:10:00

问题


Introduction

I'm trying to update QT GUI element basing on the state of calculations in embedded python script. I'm able to extract required values from python, but can't set a reference to c++ object to make it work.

The details

Let's assume python code is called (in calc.cpp) this way:

void class_name::transfer(varA, varB, varC)
{
    Py_Initialize();
    emit inprogress(70); //HERE IT WORKS
    object module = import("__main__");
    object name_space = module.attr("__dict__");
    exec_file("MyModule.py", name_space, name_space);

    object MyFunc = name_space["MyFunc"];
    object result = MyFunc(varA, varB, varC, callback);

    double ret = extract<double>(result);
    Py_Finalize();
}

void class_name::callback(double t_prog, double t_final)
{
    progr = (double)t_prog / t_final * 100;
    cout << progr; //To check if value is updating (It is)
    emit inprogress(progr); //HERE IT FAIL
}

callback is a static member function (in calc.cpp) I use to extract some values, indicating on which stage is calculation inside python script. It is called in loop from python script (MyModule.py):

while r.successful() and k < num_steps:
    r.integrate(r.t + delta_t)
    callback(r.t, t_final)

However the compilation fails with following error:

illegal call to nonstatic member function

nonstatic member reference must be relative to specific object

It is related to emit inprogress(progr);

The Question

I think I should pass a reference to the object from my c++ to python, and back to c++ with callback. But I can't find a way how to do this. What's the correct way to do this?

Tested ideas (still not working)

  1. I was trying to pass it simple like: void class_name::callback(double t_prog, double t_final, class_name &cssd) but python seems to can't convert it automatically.
  2. Creating of new class object:

    class_name cs;
    emit cs.inprogress(progr);
    

    Compile without error, but signal never reach the slot - it creates new object instead of refer to existing.


回答1:


We need to add some additional state to the callback, namely a reference to the instance of the class whose member function we want to invoke.

To do this, we can use a class. To make the functionality equivalent to just using a simple static callback function, let's define operator() (i.e. make it a functor), and also expose this operator to Python.

Let's suppose we have the following application class:

class app
{
public:
    explicit app(std::string name) : name_(std::move(name)) {}

    int run();

    void callback(double t_prog, double t_final);

private:
    std::string name_;
};

In run() we execute our Python script, and we want it to call the member function callback of the current instance.

Let's define the following callback handler class:

class callback_handler
{
public:
    explicit callback_handler(app& a) : app_(a) {}

    void operator()(double t_prog, double t_final)
    {
        app_.callback(t_prog, t_final);
    }

private:
    app& app_;
};

We need to expose this class to Python, but don't want to be able to create new instances from Python, nor do we really want it to be copied (although it doesn't really matter here, since our state only consists of references).

BOOST_PYTHON_MODULE(cbtest)
{
    bp::class_<callback_handler, boost::noncopyable>("callback_handler", bp::no_init)
        .def("__call__", &callback_handler::operator())
        ;
};

At the start of our application, we need to make sure to initialize our module before we use it -- call initcbtest(); right after initializing the Python interpreter.

Now we can use our callback handler in the following manner (The Python code stays the same, since the object is callable):

    callback_handler cbh(*this);
    bp::object result = MyFunc(1, 10, 2, boost::ref(cbh));
    std::cout << "result = " << bp::extract<double>(result) << "\n";

Sample Code

#include <boost/noncopyable.hpp>
#include <boost/python.hpp>

#include <iostream>
// ============================================================================
namespace bp = boost::python;
// ============================================================================
class app
{
public:
    explicit app(std::string name) : name_(std::move(name)) {}

    int run();

    void callback(double t_prog, double t_final);

private:
    std::string name_;
};
// ============================================================================
class callback_handler
{
public:
    explicit callback_handler(app& a) : app_(a) {}

    void operator()(double t_prog, double t_final)
    {
        app_.callback(t_prog, t_final);
    }

private:
    app& app_;
};
// ----------------------------------------------------------------------------
BOOST_PYTHON_MODULE(cbtest)
{
    bp::class_<callback_handler, boost::noncopyable>("callback_handler", bp::no_init)
        .def("__call__", &callback_handler::operator())
        ;
};
// ============================================================================
void app::callback(double t_prog, double t_final)
{
    std::cout << "CB(" << name_ << ") " << t_prog << " " << t_final << "\n";
}
// ----------------------------------------------------------------------------
int app::run()
{
    Py_Initialize();
    initcbtest();

    try {
        bp::object module = bp::import("__main__");
        bp::object name_space = module.attr("__dict__");
        bp::exec_file("MyModule.py", name_space, name_space);

        bp::object MyFunc = name_space["MyFunc"];

        callback_handler cbh(*this);
        bp::object result = MyFunc(1, 10, 2, boost::ref(cbh));
        std::cout << "result = " << bp::extract<double>(result) << "\n";
    } catch (bp::error_already_set&) {
        PyErr_Print();
    }

    Py_Finalize();

    return 0;
}
// ============================================================================
int main()
{
    app a("TestApp");

    return a.run();
}
// ============================================================================

Python Script

File MyModule.py:

def MyFunc(a, b, c, callback):
    result = 0
    for i in range(a, b, c):
        result += i
        callback(i, b)
    return result

Console Output

CB(TestApp) 0 10
CB(TestApp) 2 10
CB(TestApp) 4 10
CB(TestApp) 6 10
CB(TestApp) 8 10
result = 20



回答2:


Maybe the problem is that you are calling emit c_n.inprogress(progr);, where the argument of the signal progr is of type double, whereas in the connect(sender, SIGNAL( inprogress(int) ), ui->progressBar, SLOT( setValue(int) ) ); the signal takes an integer as argument. In older Qt versions(older than Qt5), signals and slots must use exactly the same types, meaning that the implicit conversion may not occur.

https://forum.qt.io/topic/23302/connect-diferent-signals-and-slost-each-other/4



来源:https://stackoverflow.com/questions/55010451/how-to-send-signal-using-callback-called-from-external-script

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!