前言
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。
文章评论