18.1 Creaing Threads
Qt中提供多线程的机制很简单:创建QThread的派生类,并重新实现其保护成员函数run()。
QThread::run(),被调用来开始线程的执行,在run()结束时线程终止。
QThread::terminate(),用来终止线程的执行,非阻塞操作,并不保证线程的立即终止;可以在调用QThread::terminate()之后调用QThread::wait()来实现同步等待。
terminate()并不是值得推荐结束线程的方法,因为它强制线程终止而不给线程任何清场的机会。
18.2 Synchronizing Threads
Qt提供的用于线程同步的类包括QMutex,QReadWriteLock,QSemaphore和QWaitCondition
QMutex
QMutex::lock() 阻塞操作
QMutex::trylock() 非阻塞操作
QMutex::unlock()
QMutexLocker是Qt提供的用于简化Mutex操作的一个类--QMutexLocker的构造函数以一个QMutex对象为参数,并对其自动执行lock操作;而在析构函数则对其自动执行unlock操作。
QReadWriteLock可以允许同时进行多个读操作或一个写操作。
QReadWriteLock::lockForRead()
QReadWriteLock::lockForWrite()
QReadWriteLock::unlock()
QSemaphore是对Mutex的扩展;与读写锁不同的是,信号量可以用来保护一批相同的资源,而不只是一个。
QSemaphore::acquire(int n=1)
QSemaphore::release(int n=1)
QSemaphore::available()
QWaitCondition和QMutex联合使用,可以允许一个线程在某个条件满足时唤醒其他线程,比起单独使用QMutex能实现更精确的控制。
QWaitCondition::wait()的参数是一个状态为locked的QMutex,该函数在阻塞本线程前会将这个QMutex解锁,并在函数返回前对其lock。
TLS(thread-local storage)
较好的实现方法是使用QThreadStorage< T> 类,该类常用来实现cache,这样可以避免使用mutex时lock,unlock以及等待带来的开销。
由于某些编译器的问题,QThreadStorage< T> 中只能存放指针。
QThreadStorage::hasLocalData()
QThreadStorage::setLocalData()
18.3 Communicating with MainThread
当Qt程序运行时,主线程是唯一的线程,并且是唯一允许创建QApplication或QCoreApplication对象并对其调用exec()的线程。在调用exec()之后,主线程要么是在等待event的发生,要么是在处理一个event。
主线程可以通过创建QThread的子类来开始新线程。
之前介绍的mutex,read/write lock,semaphore等均可用于新线程之间的通讯,但是却不能用于和主线程的通讯,因为这会导致主循环的event loop被阻塞并" 冻结" UI。
解决方案是在主线程与新线程之间跨线程的使用signal-slot机制。
通常情况下signal-slot机制是同步工作的,这意味着当signal被emit时,与之想联系的slot会被立即调用。
然而,当该机制用于将不同线程中的object连接起来时,则变为异步机制。这样的连接是在底层是通过创建并传递event来实现的;slot被signal的接收对象所在的线程的event loop所调用。
默认情况下,一个QObject对象存在于其被创建的线程之中;这可以在任何时候调用QObject::moveToThread()被改变。
18.4 Using Qt's Classess in Secondary Threads
Thread-safe & Reentrant 注意留意这两个概念应用在函数和类之上的不同。
对于类,如果它的所有成员函数都可以被不同线程同时调用而不相互影响--即使这些调用是针对同一个类对象,那么该类被定义为thread-safe。
对于类,如果其不同实例可以在不同线程中被同时使用而不相互影响,那么该类被定义为reentrant;然而,不同线程中同时访问同一个reentrant类对象,并不是安全的,这样的访问需要用mutex进行保护。
在Qt的定义中,在类这个层次,thread-safe是比reentrant更严格的要求,这和在函数层次上的关系正好相反。
通常情况下C++的类只要不使用全局或其它共享变量,就是reentrant的。
Qt中大多数non-GUI的类,是属于reentrant的。QObject是reentrant的,但是需要注意以下几点:
1). 子QObject必须在父QObject所属的线程中被创建,这意味着在非主线程中的对象在创建时不允许以QThread作为parent,因为后者是在主线程或另外一个非主线程中被创建的。
2). 在删除一个QThread对象前,必须将对应线程中创建的所有对象都销毁。
3). 对象必须在其被创建的线程中被删除。
如果需要删除存在于另一个线程中的对象,必须调用线程安全的QObject::deleteLater()函数,该函数会发送一个" defered delete" event。
QWidget及其子类不是reentrant的。
Qt提供了Qlibrary类,用于以一种平台无关的方式实现在程序运行时加载共享库。
19.1 Extending Qt with Plugins
Qt本身可以被很多类型的plugin扩展,最常见的包括database drivers,image formats,text codecs等。
对于每种类型的plugin,通常都需要两个类:一个wrapper类实现该类通用的plugin API,以及一个或多个handler类,每个类实现一个plugin特定的API。
在plugin的.cpp文件中,需要使用Q_EXPORT_PLUGIN()这个宏来确保plugin能够被Qt识别。
plugin真正执行的操作都是通过其handler类来实现的。
plugin的.pro文件与应用程序不同。默认情况下.pro文件使用app模板,然而这里必须使用lib模板,因为plugin属于库,而不是一个独立的应用程序。
QCoreApplication::addLibraryPath( ),为程序添加新的库路径。
19.2 Making Application Plugin-Aware
应用程序的plugin实际是实现了一个或多个接口(interface)的动态库。应用程序与plugin之间的通讯是通过interface的virtual table来完成的。
一个接口(interface)通常声明一个virtual析构函数,一个返回QStringList的virtual函数,以及一个或多个其他virtual函数。
在接口声明的尾部,需要调用Q_DECLARE_INTREFACE2()来将该interface与某个标识符关联起来。
QPluginLoader类用于在运行时加载plugin。
QPluginLoader::load(),通常不需要显式调用,因为instance()函数会在必要时调用该函数完成加载。
QPluginLoader::instance(),返回一个指向plugin对象的QObject *指针。
同一个插件plugin可以成功cast至多个interface,因为plugin可以通过多重继承来提供多个interface。
19.3 Writing Application Plugins
应用程序的plugin是其要提供的interface和QObject二者的子类。
在plugin的源代码中,需要为其提供的每个Interface都要使用Q_INTERFACES()宏,来保证moc和qobject_cast< T> 之间的协调工作。
在.cpp文件的尾部,同样需要调用Q_EXPORT_PLUGIN2()宏来使该plugin对于Qt可用。
20.1 Interfacing with Native APIs
在每个平台上,Qt都为QWidget提供了一个winId()函数,返回window ID或是句柄;QWidget还提供了一个静态函数find(),返回一个特定window ID对应的widget。我们可以将获得的window ID传递给Native API来执行平台特定的操作。
Qt定义了以下系统标志:Q_WS_WIN,Q_WS_X11,Q_WS_MAC,Q_WS_QWS(Qtopia)。
QSysInfo::WindowsVersion QSysInfo::MacintoshVersion 这两个静态变量存储着WIN和MAC操作系统的版本信息
20.2 Using ActiveX on Windows
ActiveX构建于Microst COM之上,它为使用组件的应用程序定义了一套接口,为提供组件的库和应用程序定义了另一套接口。
ActiveQt由两个模块组成:
QAxContainer模块允许用户使用COM object并在Qt程序中内嵌ActiveX控件。
QAxServer模块允许用户导出自定义的COM object以及用Qt编写的ActiveX控件。
Q_ENUMS()宏的作用是告知moc其" 宏参数" 是枚举类型。
QAxContainer模块由三个类组成:QAXObject封装一个COM object,QAxWidget封装一个ActiveX控件,QAxBase为QAxObject和QAxWidget实现核心COM功能。
QAxObject派生自QAxBase和QObject,QAxWidget派生自QAxBase和QWidget。
COM中的数据类型会被自动转换为合适的Qt数据类型。
QAxBase::setControl()
QObject::setProperty()可用于设置COM property和Qt property。
要链接QAxContainer库的话,需要在.pro文件中添加下列一行:" CONFIG +=qaxcontainer"
QAxBase::dynamicCall()
注意,QAxObject和QAxWidget的子类无法定义新的property,signal和slot。
QAxServer模块允许将一个标准Qt程序转换为一个ActiveX server。该server可以是共享库,也可以是独立的应用程序。共享库形式的server被称为in-process servers,而独立应用程序形式的server被称为out-of-process server。
QAxBindable在widget与ActiveX client之间提供了一个接口。
在Qt中处理多重继承中,如果基类中存在QObject的派生类,必须将这样的类放在首位。
QAXFACTORY_DEFAULT()宏的作用是导出一个AxtiveX控件,可以用于仅导出一个控件的ActiveX server;当server要导出多个控件时,不能使用QAXFACTORY_DEFAULT()宏。
QApplication能够识别命令行中的-activex参数,并使应用程序作为server而运行。
Q_CLASSINFO()宏
20.3 Handling X11 Session Management
为了使一个Qt/X11应用程序意识到session manager的存在,需要重新实现QApplication::saveState()函数,并在该函数中保存应用程序的状态信息。
当用户启动shutdown操作时,程序员可以通过重新实现QApplication::commitData()来获取控制权,这允许应用程序保存未保存的数据,并且与用户交互--如果可能的话;这部分session management在X11和Windows上都被支持。
QObject::setObjectName()
void QApplicatoin::saveState(QSessionManager &)--该函数在session manager希望应用程序保存其状态时被调用,QSessionManager类型的参数允许应用程序与session manager进行通讯。
discard command:是指session manager必须执行的用删除任何存储当前状态信息的命令。
restart command:是指session manager必须执行的用以重新启动应用程序的命令。
QSessionManager::setDiscardCommand(QStringList &)
QSessionManager::setDiscardCommand(QStringList & )
默认情况下,Qt提供的restart command的格式为: appname -session id_key
QSessionManager::release()
QSessionManager::cancel()
QApplication:isSessionRestored()