如何从一个非Qt C++库类向Qt GUI提供反馈?

3

我正在为一些计算密集型任务(机器视觉)开发一个C++类库。

// I am a part of a Qt-agnostic library
class Cruncher
{
    /* ... */
public:
    void doStuff();
};

然后有一个使用该库的Qt GUI。我正在创建一个工作线程,从库中调用重型例程:

// I am a part of a Qt-based GUI which utilizes the library
class Worker : public QThread
{
    /* ... */
protected:
    virtual void run()
    {
        /* ... */
        Cruncher c;
        for (int i = 0; i < count; ++i)
            c.doStuff(); // takes some time, and while it's working 
                         // it should communicate status changes which should
                         // become visible in the GUI
    }
};

现在在doStuff()函数内部进行了很多操作,我希望在等待doStuff()返回的同时向用户提供一些反馈。例如,可能需要更精细的进度报告,而不仅仅是在每次调用doStuff()后将进度表增加一个步骤。此外,当Cruncher正在工作(且Worker当前正在调用doStuff())时,doStuff()可能遇到非关键性故障,但我希望出现一条消息在GUI中报告这种情况。

我希望该库保持与Qt无关,因此不愿为Cruncher添加信号和槽。还有其他方法可以使其能够在不是Qt类时向GUI提供反馈以报告其工作吗?

我考虑创建一个QTimer,在Worker运行时固定间隔轮询Cruncher的某些“状态”和“errorMsg”成员,但这似乎极为不理想。


你已经有了QThread,所以它不是非常独立于Qt... - hyde
1
QThread在GUI中,而不是库中。我希望该库(一个独立的项目,可用于其他非交互式、非GUI程序)保持Qt独立。 - neuviemeporte
3个回答

4

我发表自己的答案,因为虽然我采纳了@Nim的建议,但我希望回答能更详细,这样如果有人遇到相同的问题就更有用。

我在库中创建了一个消息分发器的框架:

// doesn't need to know about Qt
class MessagePort
{
public:
    virtual void message(std::string msg) = 0;
};

接下来,我向Cruncher对象添加了一个句柄,并在doStuff()中不时调用message()。
// now with Super Cow powers!
class Cruncher
{
protected:
    MessagePort *msgPort_;

public:
    Cruncher(MessagePort *msgPort) : msgPort_(msgPort) {}
    void doStuff()
    {
        while(...)
        {
            /*...*/
            msgPort_->message("Foo caused an overload in Bar!");
        }
    }
};

最后,我使用所有必要的Qt技术在GUI内实现了MessagePort的功能:
class CruncherMsgCallback : public QObject, public MessagePort
{
    Q_OBJECT

public:
    CruncherMsgCallback() : QObject(), MessagePort()
    {
        connect(this, SIGNAL(messageSignal(const QString &)), 
                GUI,    SLOT(messageShow(const QString &)), 
                Qt::QueuedConnection);
    }

    virtual void message(std::string msg)
    {
        emit messageSignal(QString::fromStdString(msg));
    }

signals:
    void messageSignal(const QString &msg);
};

最后,当Worker创建Cruncher实例时,它也会为其提供一个指向可用MessagePort的指针。
class Worker
{
protected:
    virtual void run()
    {
        CruncherMsgCallback msgC;
        Cruncher c(&msgC); // &msgC works as a pointer to a 
                           // generic MessagePort by upcasting
        c.doStuff(); // Cruncher can send messages to the GUI 
                     // from inside doStuff()
    }
};

1
+1,我也有一份日常工作... :) 如果它能够完成任务,并且清晰地分离组件,并且意图明确,那么这是一个好的解决方案... - Nim
@Nim:谢谢你的想法,我很开心自己尝试着去理解它 :) - neuviemeporte

1
使用回调函数(类)等,并在构造期间传递该函数。需要报告的事项,请通过该回调函数进行报告。

好的,但回调函数应该是GUI的一部分还是库的一部分呢?如果是前者,我如何避免在库中包含GUI原型?如果是后者,我如何在不知道GUI任何信息的情况下与其通信?您能否提供一个(伪)代码示例? - neuviemeporte

1
你可以安全地在run()方法中发出信号,我认为这是从工作线程向主线程传递信息的最佳方式。只需将信号添加到您的QThread子类中(避免添加插槽,如果您对QThread线程如何工作有任何疑问)。
最好从这些信号进行显式排队连接,以避免问题。尽管默认的自动连接类型也应该可以工作并执行排队信号发射,但我认为在这种情况下更好的做法是明确指定。实际上,直接信号也应该可以工作,但是这样你就必须自己处理线程安全性,而不是让Qt为你处理,并且你不能连接使用任何QtGui类的插槽,这些类只能在主线程中工作,因此最好坚持使用排队连接。
要将简单信息传递给run()方法,并且如果不需要立即反应,也许可以使用一些共享的QAtomicInt变量或类似标志,工作线程在方便时检查。稍微复杂一些的方法,仍需要轮询,是具有互斥保护的共享数据结构。更复杂的沟通方式涉及某种消息队列(就像Qt在主线程的事件循环中使用的那样,当您向该方向发出信号时)。

问题在于,run() 方法被卡在调用 doStuff() 上,直到当前的 doStuff() 调用终止之前它都不会变得自由。我需要在 doStuff() 运行时发送消息。 - neuviemeporte
@neuviemeporte 好的,doStuff() 方法如何接收消息?有哪些可用的线程间通信操作/函数/库?你可以在问题中具体说明。 - hyde
doStuff() 是信息的源头;当它认为某些操作是不可能的时,会跳过一些工作。它所属的库可以访问所有的libstc ++和任何其他库,但不能使用Qt。很抱歉没有表述清楚。 - neuviemeporte
我正准备写一些关于回调接口类的内容,但你已经写了一个答案,做得很好... 就是这样 :) - hyde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接