如何在动态链接库中使用QT库

2023年6月20日 1097点热度 0人点赞 0条评论

前言

​ QT除了提供GUI上的一些组件,还提供了一些很好用的非GUI的库,有时候为了方便(主要是习惯了使用QT)需要将QT的一些类再次封装,提供给一个非QT GUI的工程使用。

​ 如果不需要使用到QT的事件循环相关的功能,那使用起来跟其他的库没有什么区别。

​ 但是QT很多类都提供了信号和槽,并且QT在封装的时候有些类是默认使用了事件循环的功能的,那么这个时候使用就需要注意一些问题。

QCoreAppLication 和QApplication 的区别

​ QApplication是QCoreApplication的派生类,它封装了GUI相关的事件循环,并且它只能在主线程被创建,这里的主线程指的是程序入口的线程,否则运行时会抛出异常。

​ 在没有使用GUI库时建议使用QCoreApplication,它可以在其他线程被创建,被对象被创建的线程会被App对象认为是“主线程”,App->exec();函数只能在这个主线程中被调用,所有默认的事件循环也将都会在这个线程里被处理。

​ 注意:在创建QCoreApplication对象之前,最好是不要创建任何QObject对象,也不要调用任何的QT相关的APi,因为如果QObject对象被创建,将会把所在的线程认为是“主线程”,如果QCoreApplication不是在这个线程被创建的,那么创建时就会抛出错误。并且由于App对象还没有被创建,有一些类会创建失败。

在动态链接库中使用qt

​ 在动态链接库中,大多数时候是外部程序通过调用动态链接库的接口来实现某项功能,这些函数在什么线程中被调用是未知,所以并不能在这些接口函数中直接处理事件循环,并且处理事件循环会导致线程一直阻塞也是不合理的。

​ 鉴于上述的原因,可以在开发者使用动态链接库之前,在开启一个子线程,在线程中让App对象进入事件循环。

void StartApplication(){
    int argc = 1;
    char*argv[] = {"fuckQQ",nullptr};
    QCoreApplication a(argc,argv);
    a.exec();
}

void init()
{
    std::thread th(StartApplication);
    th.detach();
}

​ 如上述代码,可以将init()函数暴露到外部,让开发者在使用动态库中的功能之前先调用init()函数。

​ 当App对象在子线程中处理事件循环时,有些Qt类在使用时需要注意一些问题。

​ 对事件循环有相当强依赖的类,列如在Qt 4.8中的QLocalSocket,在创建对象时,如果该线程不是App对象所在的线程,或者App对象并没有被创建,那么都会导致对象创建失败(但是似乎在Qt6中已经没有这个问题了)。

​ 造成这个问题的原因,我猜测是因为信号跟槽的触发机制,当信号与槽被绑定为列队的方式触发时,如果创建对象的线程中没有处理事件循环的App对象,那么槽函数永远不会触发,我甚至猜测qt的事件处理也有类似的机制。

​ 所以在接口封装时,如果对Qt类有跨线程的操作,最好时用信号,通知事件循环线程来创建对象,并且可以将对象的读取写入,都可以通过信号传递到事件循环线程中处理,这样既可以保证线程安全,也可以保证所有的信号与槽都可以被正常的触发。

补充

1. QT 4.8 (其他版本未测试)windows 10系统上,QLocalSocket对象的创建线程不是创建App的线程那么会报错 :Cannot create a win event notifier without a QEventDispatcherWin32。

大脸猫

这个人虽然很勤快,但什么也没有留下!

文章评论