如何使用从外部脚本调用的回调发送信号?

Posted

技术标签:

【中文标题】如何使用从外部脚本调用的回调发送信号?【英文标题】:How to send signal using callback called from external script? 【发布时间】:2019-03-05 19:48:16 【问题描述】:

简介

我正在尝试根据嵌入式 python 脚本中的计算状态更新 QT GUI 元素。我能够从 python 中提取所需的值,但无法设置对 c++ 对象的引用以使其工作。

详情

假设 Python 代码是这样调用的(在 calc.cpp 中):

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 是一个static 成员函数(在 calc.cpp 中)我用来提取一些值,指示在 python 脚本中的哪个阶段进行计算。它从 python 脚本(MyModule.py)循环调用:

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

但是编译失败并出现以下错误:

非法调用非静态成员函数

非静态成员引用必须是相对于特定对象的

emit inprogress(progr);相关

问题

我认为我应该将对象的引用从我的 c++ 传递给 python,然后通过回调返回给 c++。但我找不到如何做到这一点的方法。这样做的正确方法是什么?

经过测试的想法(仍然无效)

    我试图简单地传递它,例如: void class_name::callback(double t_prog, double t_final, class_name &amp;cssd) 但是python好像不能自动转换。

    创建新的类对象:

    class_name cs;
    emit cs.inprogress(progr);
    

    编译没有错误,但信号永远不会到达插槽 - 它创建新对象而不是引用现有对象。

【问题讨论】:

将信号连接到插槽的代码在哪里? 我将它包含在编辑中。 我根据我的实际知识修改了这个问题。 使回调成为一个对象,该对象将持有对class_name 实例的引用。给它一个像dispatch(double t_prog, double t_final) 这样的方法,它将执行instance.callback(t_prog, t_final)(现有的callback 成为非静态成员——可能想要重命名它)。 |或者你可以只提供一个指针作为现有静态回调函数的额外参数,但这不是很好。 你去。关于你的想法 - #1 需要类 class_name 暴露给 Python(至少是类本身,而不是任何成员函数)。 #2 不起作用,因为您创建了一个新实例(但您需要的是原始实例)。 【参考方案1】:

我们需要为回调添加一些额外的状态,即对我们要调用其成员函数的类的实例的引用。

为此,我们可以使用一个类。为了使功能等同于使用简单的静态回调函数,让我们定义operator()(即使其成为函子),并将此运算符公开给 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_;
;

run()中我们执行我们的Python脚本,我们希望它调用当前实例的成员函数callback

让我们定义以下回调处理程序类:

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_;
;

我们需要向 Python 公开这个类,但不希望能够从 Python 创建新实例,也不希望它被复制(尽管在这里并不重要,因为我们的状态只是由参考组成)。

BOOST_PYTHON_MODULE(cbtest)

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

在我们的应用程序开始时,我们需要确保在使用它之前初始化我们的模块——在初始化 Python 解释器之后立即调用initcbtest();

现在我们可以通过以下方式使用我们的回调处理程序(Python 代码保持不变,因为对象是可调用的):

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

示例代码

#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 脚本

文件MyModule.py

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

控制台输出

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

【讨论】:

解决了这个问题。我也在尝试使用 functor 运气,但我之前从未使用过它,所以我犯了一些基本错误。上面介绍的实现效果很好——我只需要在 .cpp 而不是 .h 中插入 BOOST_PYTHON_MODULE(cbtest),因为多个声明存在一些奇怪的问题。感谢您的帮助! @Karls 是的,BOOST_PYTHON_MODULE 不能在标题中——如果你看the implementation,它会生成模块初始化函数(在这种情况下是initcbtestinit_module_cbtest)并且您提供第二个函数的主体。由于函数不是inline,因此将其放在标头中可能会导致它们被多次定义。【参考方案2】:

也许问题在于您正在调用emit c_n.inprogress(progr);,其中信号progr 的参数类型为double,而在connect(sender, SIGNAL( inprogress(int) ), ui-&gt;progressBar, SLOT( setValue(int) ) ); 中,信号采用整数作为参数。在较旧的 Qt 版本(早于 Qt5)中,信号和槽必须使用完全相同的类型,这意味着可能不会发生隐式转换。

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

【讨论】:

我也认为这可能是问题所在,所以我什至尝试使用硬编码整数来做emit c_n.inprogress(50)。没有改变。正如你在上面看到的,我使用了静态函数,所以可能我需要一个对象的引用,但我不知道如何传递它,因为回调不是从 c++ 调用的,而是从 python 调用的。 所以在emit inprogress(progr);线上调用错误?因为问题可能是被调用的函数 setValue(int) 试图设置类成员的值,但它无权这样做,因为它没有被定义为同一类的成员。没有剩下的代码很难说 是的,错误正是在这条车道上调用的。我可以从非静态函数调用它,例如从transfer。例如:Py_Initialize(); emit inprogress(70); - 这将起作用。但是当我调用回调(所以是静态函数)时,它会返回上述错误。 它以相同的结尾 - illegal call to nonstatic member function。我创建了函数class_name::emit_callback(double progr),并以这种方式从class_name::callback 调用它:emit_callback(progr); 所以它仍然错过了对象,没有任何改变。 感谢您的努力。是的,我知道这个技巧,但它似乎创建了新对象而不是引用现有对象。信号无处可去,永远不会到达插槽。在我提供此“编辑”之前,您甚至可以在答案中看到残留物 (emit c_n.inprogress(progr);)。所以这不是一个真正的解决方案——它只是防止错误,但信号永远不会达到它的目标。您可以自己测试它 - 在一个文件中创建简单的插槽,从第二个文件调用一个方法,并尝试从第二个文件中的静态函数发出信号 - 它不会出错,但也没有效果。

以上是关于如何使用从外部脚本调用的回调发送信号?的主要内容,如果未能解决你的问题,请参考以下文章

C语言中,用于设置中断、中断信号的函数都有哪些?怎么设置一个发送中断信号(自己定义的)的函数?

使用 QRunnable 进行线程处理 - 发送双向回调的正确方式

Unity 中的回调侦听器 - 如何从 Android 中的 UnityPlayerActivity 调用脚本文件方法

如何从 .gitconfig 别名调用外部脚本?

如何创建脚本或 Flyway 可以配置为每次使用 SQL 回调调用它?

js回调函数内怎么调用外部this