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的。
Chapter 19 Creating Plugins
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可用。
Chapter 20 Platform-Specific Features
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()