這次討論Qt與Web混合開發相關技術。
這類技術存在適用場景,例如:Qt項目使用Web大量現成的組件/方案做功能擴展,
Qt項目中性能無關/頻繁更新迭代的頁面用html單獨實現,Qt項目提供Web形式的SDK給
用戶做二次開發等等,或者是Web開發人員齊全而Qt/C++人手不足,此類非技術問題,
都可以使用Qt + Web混合開發。
(不適用的請忽略本文)
上次的文章《Qt與Web混合開發》,討論了Qt與Web混合開發相關技術。
這次通過一個web控制小車的案例,繼續討論相關技術。
本文會先介紹Qt與Web嵌套使用,再介紹Qt與Web分開使用,之后著重討論分開使用
的一些實現細節,特別是WebChannel通信、WebChannel在Web/typescript中的使用。
這里以Qt官方的例子MiniBrowser來說明吧。
打開方式如下:
運行效果如下:
這個例子是在Qml中嵌套了WebView。
濤哥做了一個簡單的半透明測試。
增加了兩個半透明的小方塊,藍色的在WebView上面,紅色的在WebView下面。
運行效果也是正確的:
代碼是這樣的:
紅色框中是我增加的代碼。
為什么要做半透明測試呢?根據以往的經驗,不同渲染方式的兩種窗口/組件嵌套在一起,總會出現透明失效之類的問題,例如 qml與Widget嵌套。
濤哥翻了一下Qt源碼,了解到渲染的實現方式,Windows平臺大致如下:
chromium在單獨的進程處理html渲染,并將渲染結果存儲在共享內存中;主窗口在需要重繪的時候,從共享內存中獲取內容并渲染。
這里的WebView內部封裝好了WebEngine,其本身也是一個Item,就和普通的Qml一樣,屬性綁定、js function都可以正常使用,暫時不深入討論了。
Qt與Web分離,就是字面意思,Web在單獨的瀏覽器或者App中運行,不和Qt堆在一起。兩者通過socket進行通信。
這里用我自己做的例子來說明吧。
先看看效果:
左邊是Qt實現的一個簡易小車,可以前進和轉向。右邊是Html5實現的控制端,控制左邊的小車。
源碼在github上: https://github.com/jaredtao/QtWeb
小車來自Qt的D-Bus Remote Controller 例子
原版的例子,實現了通過QDBus 跨進程 控制小車。
(吐槽:這是一個古老的例子,使用了GraphicsView 和QDBus)
(知識拓展1: DBus是unix系統特有的一種進程間通信機制,使用有些復雜。Qt對DBus機制進行了封裝/簡化,即QDBus模塊,
通過xml文件的配置后,把DBus的使用轉換成了信號-槽的形式。類似于現在的Qt Remote Objects)
(知識拓展2: Windows本身不支持DBus,網上有socket模擬DBus的方案。參考: https://www.freedesktop.org/wiki/Software/dbus/)
我做了一些修改,主要如下:
這里貼一些關鍵代碼
Car的頭文件:
其中要說明的是:
speed和angle屬性具備 讀、寫、change信號。
還有加速、減速、左轉、右轉四個公開的槽函數。
WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。
WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,并進行雙向數據傳輸。
Qt為我們封裝好了WebSocket,即QWebSocket和QWebSocketServer,簡單易用。
如果你了解socket編程,就看作TCP好了;如果不了解,請先去補充一下知識吧。
按濤哥的理解,WebChannel是在socket上建立的一種通信協議,這個協議的作用是把QObject暴露給遠端的HTML。
大致使用流程:
在使用WebChannel的時候,Qt端建立了WebSocketServer,之后要把server的路徑(例如:ws://127.0.0.1:12345)告訴Html。
一般就是在打開Html的時候帶上Query參數,例如: F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345
Qml中有 Qt.openUrlExternally, C++ 中有 QDesktopServices::openUrl,本質一樣, 都可以打開一個本地的html網頁。
其在Windows平臺的底層實現是Win32 API。這里有個Win32 API的缺陷,傳Query參數會被丟掉。
濤哥找到了替代的方案:
.net framework / .net core有個啟動進程的函數: System.Diagnostics.Process::Start, 可以調用瀏覽器并傳query參數
//C# 啟動chrome
System.Diagnostics.Process.Start('chrome', 'F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345');
//C# 啟動firefox
System.Diagnostics.Process.Start('firefox', 'F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345');
//C# 啟動IE
System.Diagnostics.Process.Start('IExplore', 'F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345');
Qt中直接寫C#當然不太好,不過呢,Win7/Win10 系統都帶有Powershell,而powershell依賴于.net framework, 我們可以調用powershell來間接使用.net framework。
所以有了下面的代碼:
...
QString psCmd=QString("powershell -noprofile -command \"[void][System.Diagnostics.Process]::Start('%1', '%2')\"").arg(browser).arg(url.toString());
bool ok=QProcess::startDetached(psCmd);
qWarning() << psCmd;
if (!ok) {
qWarning() << "failed";
}
...
結果完美運行。
Web端就按照Web常規流程開發。
Web部分的源碼也在前文提到的github倉庫,子路徑是QtWeb\WebChannelCar\Web
如下是Web部分的目錄結構:
腳本用typescript,包管理用npm,打包用webpack,編輯器用vs code, 都中規中矩。
內容比較簡單,暫時不需要前端框架,手(復)寫(制)的html和css。
html部分比較簡單
//index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; chartset=utf-8" />
<link rel="stylesheet" type="text/css" href="../style/style.css" />
<link rel="stylesheet" type="text/css" href="../style/layout.css" />
</head>
<body>
<button id="up" class="green button">加速</button>
<button id="down" class="red button">減速</button>
<button id="left" class="blue button">左轉</button>
<button id="right" class="blue button">右轉</button>
<img id="img" src="../img/disconnected.svg" />
<div>
<div>
<label>速度: </label>
<label id="speed">0</label>
</div>
<div>
<label>角度: </label>
<label id="angle">0</label>
</div>
</div>
</body>
<script src="../out/main.js">
</script>
</html>
樣式和布局全靠css,這里就不貼了。
腳本部分需要細說了。
src文件夾為全部腳本,目錄結構如下:
從main開始, 加點注釋:
//main.ts
import WebChannelCore from "./webchannelCore";
//window加載時回調,入口
window.onload=()=> {
//初始化WebChannel,傳參為兩個回調,分別對應WebChannel建立連接和連接斷開。
WebChannelCore.initialize(onInit, onUninit);
}
//WebChannel建立連接的處理
function onInit() {
//換圖標
(window as any).document.getElementById("img").src="../img/connected.svg";
//獲取QObject對象
let car=WebChannelCore.SDK.car;
//取dom樹上的組件
let upBtn=(window as any).document.getElementById("up");
let downBtn=(window as any).document.getElementById("down");
let leftBtn=(window as any).document.getElementById("left");
let rightBtn=(window as any).document.getElementById("right");
let speedLabel=(window as any).document.getElementById("speed");
let angleLabel=(window as any).document.getElementById("angle");
//綁定按鈕點擊事件
upBtn.onclick=()=> {
//調用QObject的接口
car.accelerate();
}
downBtn.onclick=()=> {
car.decelerate();
}
leftBtn.onclick=()=> {
car.turnLeft();
}
rightBtn.onclick=()=> {
car.turnRight();
}
//QObject的信號連接到js 回調
car.speedChanged.connect(onSpeedChanged);
car.angleChanged.connect(onAngleChanged);
}
//WebChannel斷開連接的處理
function onUninit() {
//換圖標
(window as any).document.getElementById("img").src="../img/disconnected.svg";
}
//異步更新 speed
async function onSpeedChanged() {
let speedLabel=(window as any).document.getElementById("speed");
let car=WebChannelCore.SDK.car;
//獲取speed,異步等待。
//注意這里改造過qwebchannel.js,才能使用await。
speedLabel.textContent=await car.getSpeed();
}
//異步更新 angle
async function onAngleChanged() {
let angleLabel=(window as any).document.getElementById("angle");
let car=WebChannelCore.SDK.car;
//獲取angle,異步等待。
//注意這里改造過qwebchannel.js,才能使用await。
angleLabel.textContent=await car.getAngle();
}
可以看到我們從WebChannelCore.SDK 中獲取了一個car對象,之后就當作QObject來用了,包括調用它的函數、連接change信號、訪問屬性等。
這一切都得益于WebSocket/WebChannel.
接下來看一下WebChannelCore的實現
//WebChannelCore.ts
import { QWebChannel } from './qwebchannel';
type callback=()=> void;
export default class WebChannelCore {
public static SDK: any=undefined;
private static connectedCb: callback;
private static disconnectedCb: callback;
private static socket: WebSocket;
//初始化函數
public static initialize(connectedCb: callback=()=> { }, disconnectedCb: callback=()=> { }) {
if (WebChannelCore.SDK !=undefined) {
return;
}
//保存兩個回調
WebChannelCore.connectedCb=connectedCb;
WebChannelCore.disconnectedCb=disconnectedCb;
try {
//調用link,并傳入兩個回調參數
WebChannelCore.link(
(socket)=> {
//socket連接成功時,創建QWebChannel
QWebChannel(socket, (channel: any)=> {
WebChannelCore.SDK=channel.objects;
WebChannelCore.connectedCb();
});
}
, (error)=> {
//socket出錯
console.log("socket error", error);
WebChannelCore.disconnectedCb();
});
} catch (error) {
console.log("socket exception:", error);
WebChannelCore.disconnectedCb();
WebChannelCore.SDK=undefined;
}
}
private static link(resolve: (socket: WebSocket)=> void, reject: (error: Event | CloseEvent)=> void) {
//獲取Query參數中的websocket地址
let baseUrl="ws://localhost:12345";
if (window.location.search !="") {
baseUrl=(/[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(window.location.search)![1]);
}
console.log("Connectiong to WebSocket server at: ", baseUrl);
//創建WebSocket
let socket=new WebSocket(baseUrl);
WebChannelCore.socket=socket;
//WebSocket的事件處理
socket.onopen=()=> {
resolve(socket);
};
socket.onerror=(error)=> {
reject(error);
};
socket.onclose=(error)=> {
reject(error);
};
}
}
(window as any).SDK=WebChannelCore.SDK;
這部分代碼不復雜,主要是連接WebSocket,連接好之后創建一個QWebChannel。
觀察仔細的同學會發現,src文件夾下面,沒有叫‘qwebchannel.ts’的文件,而是‘qwebchannel.js’,和一個‘qwebchannel.d.ts’
這涉及到另一個話題:
‘qwebchannel.js’是Qt官方提供的,在js中用足夠了。
而我們這里是用TypeScript,按照TypeScript的規則,直接引入js是不行的,需要一個聲明文件 xxx.d.ts
所以我們增加了一個qwebchannel.d.ts文件。
(熟悉C/C++的同學,可以把d.ts看作typescript的頭文件)
內容如下:
//qwebchannel.d.ts
export declare function QWebChannel(transport: any, initCallback: Function): void;
只是導出了一個函數。
這個函數的實現在‘qwebchannel.js’中:
//qwebchannel.js
"use strict";
var QWebChannelMessageTypes={
signal: 1,
propertyUpdate: 2,
init: 3,
idle: 4,
debug: 5,
invokeMethod: 6,
connectToSignal: 7,
disconnectFromSignal: 8,
setProperty: 9,
response: 10,
};
var QWebChannel=function(transport, initCallback)
{
if (typeof transport !=="object" || typeof transport.send !=="function") {
console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +
" Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));
return;
}
...
}
function QObject(name, data, webChannel)
{
...
}
這個代碼比較長,就不全部貼出來了。主要實現了兩個類,QWebChannel和QObject。
QWebChannel就是用來接管websocket的,而QObject是用js Object模擬的 Qt的 QObject。
這一塊不細說了,感興趣的同學可以自己去研究源碼。
Qt默認的qwebchannel.js在實際使用過程中,有些不好的地方,就是函數的返回值不是直接返回,而是要在回調函數中獲取。
比如car.getAngle要這樣用:
let angle=0;
car.getAngle((value:number)=> {
angle=value;
});
我們的實際項目中,有大量帶返回值的api,這樣的用法每次都嵌套一個回調函數,很不友好,容易造成回調地獄。
我們同事的解決方案是,在typescript中把這些api再用Promise封裝一層,外面用await調用。
例如這樣封裝一層:
function getAngle () {
return new Promise((resolve)=>{
car.getAngle((value:number)=> {
resolve(value);
});
});
}
使用和前面的代碼一樣:
//異步更新 angle
async function onAngleChanged() {
let angleLabel=(window as any).document.getElementById("angle");
let car=WebChannelCore.SDK.car;
//獲取angle,異步等待。
//注意這里改造過qwebchannel.js,才能使用await。
angleLabel.textContent=await car.getAngle();
}
這種解決方案規避了回調地獄,但是工作量增加了。
濤哥思考良久,稍微改造一下qwebchannel.js,自動把Promise加進去,也不需要再額外封裝了。
我們在Qt 程序中寫了QObject,然后暴露給了ts。
在ts這邊,一般也需要提供一個聲明文件,明確有哪些api可用。
例如我們的car聲明:
//CarObject.ts
declare class Car {
get speed():number;
set speed(value:number);
get angle():number;
set angle(vlaue:number);
public accelerate():void;
public decelerate():void;
public turnLeft():void;
public turnRight():void;
}
這里濤哥寫了一個小工具,能夠解析Qt中的QObject,并生成對應的ts文件。
【領QT開發教程學習資料,點擊下方鏈接莬費領取↓↓,先碼住不迷路~】
點擊這里:「鏈接」
文源自外部鏈接,下次造輪子前先看看現有的輪子吧,值得學習的C語言開源項目
Webbench是一個在linux下使用的非常簡單的網站壓測工具。它使用fork()模擬多個客戶端同時訪問我們設定的URL,測試網站在壓力下工作的性能,最多可以模擬3萬個并發連接去測試網站的負載能力。Webbench使用C語言編寫, 代碼實在太簡潔,源碼加起來不到600行。
tinyhttpd是一個超輕量型Http Server,使用C語言開發,全部代碼只有502行(包括注釋),附帶一個簡單的Client,可以通過閱讀這段代碼理解一個 Http Server 的本質。
好玩,有趣,專業C/C++學習交流,源碼下載 群:747821062
cJSON是C語言中的一個JSON編解碼器,非常輕量級,C文件只有500多行,速度也非常理想。
cJSON也存在幾個弱點,雖然功能不是非常強大,但cJSON的小身板和速度是最值得贊賞的。其代碼被非常好地維護著,結構也簡單易懂,可以作為一個非常好的C語言項目進行學習。
cmockery是google發布的用于C單元測試的一個輕量級的框架。它很小巧,對其他開源包沒有依賴,對被測試代碼侵入性小。cmockery的源代碼行數不到3K,你閱讀一下will_return和mock的源代碼就一目了然了。
主要特點:
libev是一個開源的事件驅動庫,基于epoll,kqueue等OS提供的基礎設施。其以高效出名,它可以將IO事件,定時器,和信號統一起來,統一放在事件處理這一套框架下處理。基于Reactor模式,效率較高,并且代碼精簡(4.15版本8000多行),是學習事件驅動編程的很好的資源。
Memcached 是一個高性能的分布式內存對象緩存系統,用于動態Web應用以減輕數據庫負載。它通過在內存中緩存數據和對象來減少讀取數據庫的次數,從而提供動態數據庫驅動網站的速度。Memcached 基于一個存儲鍵/值對的 hashmap。Memcached-1.4.7的代碼量還是可以接受的,只有10K行左右。
下載地址:http://memcached.org/
Lua很棒,Lua是巴西人發明的,這些都令我不爽,但是還不至于臉紅,最多眼紅。
讓我臉紅的是Lua的源代碼,百分之一百的ANSI C,一點都不摻雜。在任何支持ANSI C編譯器的平臺上都可以輕松編譯通過。我試過,真是一點廢話都沒有。Lua的代碼數量足夠小,5.1.4僅僅1.5W行,去掉空白行和注釋估計能到1W行。
好玩,有趣,專業C/C++學習交流,源碼下載 群:747821062
SQLite是一個開源的嵌入式關系數據庫,實現自包容、零配置、支持事務的SQL數據庫引擎。 其特點是高度便攜、使用方便、結構緊湊、高效、可靠。足夠小,大致3萬行C代碼,250K。
好玩,有趣,專業C/C++學習交流,源碼下載 群:747821062
UNIX V6 的內核源代碼包括設備驅動程序在內 約有1 萬行,這個數量的源代碼,初學者是能夠充分理解的。有一種說法是一個人所能理解的代碼量上限為1 萬行,UNIX V6的內核源代碼從數量上看正好在這個范圍之內。看到這里,大家是不是也有“如果只有1萬行的話沒準兒我也能學會”的想法呢?
另一方面,最近的操作系統,例如Linux 最新版的內核源代碼據說超過了1000 萬行。就算不是初學者,想完全理解全部代碼基本上也是不可能的。
NetBSD是一個免費的,具有高度移植性的 UNIX-like 操作系統,是現行可移植平臺最多的操作系統,可以在許多平臺上執行,從 64bit alpha 服務器到手持設備和嵌入式設備。NetBSD計劃的口號是:”Of course it runs NetBSD”。它設計簡潔,代碼規范,擁有眾多先進特性,使得它在業界和學術界廣受好評。由于簡潔的設計和先進的特征,使得它在生產和研究方面,都有卓越的表現,而且它也有受使用者支持的完整的源代碼。許多程序都可以很容易地通過NetBSD Packages Collection獲得。
關于 C++ 框架、庫和資源的一些匯總列表,內容包括:標準庫、Web應用框架、人工智能、數據庫、圖片處理、機器學習、日志、代碼分析等。
標準庫
C++標準庫,包括了STL容器,算法和函數等。
框架
C++通用框架和庫
人工智能
異步事件循環
音頻
音頻,聲音,音樂,數字化音樂庫
生態學
生物信息,基因組學和生物技術
壓縮
壓縮和歸檔庫
并發性
并發執行和多線程
容器
密碼學
數據庫
數據庫,SQL服務器,ODBC驅動程序和工具
調試
調試庫, 內存和資源泄露檢測,單元測試
游戲引擎
圖形用戶界面
圖形
圖像處理
國際化
Jason
日志
機器學習
數學
多媒體
網絡
物理學
動力學仿真引擎
機器人學
科學計算
腳本
序列化
視頻
虛擬機
Web應用框架
XML
XML就是個垃圾,xml的解析很煩人,對于計算機它也是個災難。這種糟糕的東西完全沒有存在的理由了。-Linus Torvalds
多項混雜
一些有用的庫或者工具,但是不適合上面的分類,或者還沒有分類。
軟件
用于創建開發環境的軟件
編譯器
C/C++編譯器列表
在線編譯器
在線C/C++編譯器列表
調試器
C/C++調試器列表
集成開發環境(IDE)
C/C++集成開發環境列表
構建系統
靜態代碼分析
提高質量,減少瑕疵的代碼分析工具列表
1、元對象系統簡介
Qt的信號槽和屬性系統基于在運行時進行內省的能力,所謂內省是指面向對象語言的一種在運行期間查詢對象信息的能力, 比如如果語言具有運行期間檢查對象型別的能力,那么是型別內省(type intropection)的,型別內省可以用來實施多態。
'C++'的內省比較有限,僅支持型別內省, 'C++'的型別內省是通過運行時類型識別(RTTI)(Run-Time Type Information)中的typeid 以及 dynamic_cast關鍵字來實現的。
Qt拓展了’C++'的內省機制,但并沒有采用’C++'的RTTI,而是提供了更為強大的元對象(meta object)機制,來實現內省機制。基于內省機制,可以列出對象的方法和屬性列表,并且能夠獲取有關對象的所有信息,如參數類型。如果沒有內省機制,QtScript和 QML是難以實現的。
Qt中的元對象系統全稱Meta Object System,是一個基于標準’C++'的擴展,為Qt提供了信號與槽機制、實時類型信息、動態屬性系統。元對象系統基于QObject類、Q_OBJECT宏、元對象編譯器MOC實現。
A、QObject 類
作為每一個需要利用元對象系統的類的基類。
B、Q_OBJECT宏
定義在每一個類的私有數據段,用來啟用元對象功能,比如動態屬性、信號和槽。
在一個QObject類或者其派生類中,如果沒有聲明Q_OBJECT宏,那么類的metaobject對象不會被生成,類實例調用metaObject()返回的就是其父類的metaobject對象,導致的后果是從類的實例獲得的元數據其實都是父類的數據。因此類所定義和聲明的信號和槽都不能使用,所以,任何從QObject繼承出來的類,無論是否定義聲明了信號、槽和屬性,都應該聲明Q_OBJECT 宏。
C、元對象編譯器MOC (Meta Object Complier),
MOC分析C++源文件,如果發現在一個頭文件(header file)中包含Q_OBJECT 宏定義,會動態的生成一個moc_xxxx命名的C++源文件,源文件包含Q_OBJECT的實現代碼,會被編譯、鏈接到類的二進制代碼中,作為類的完整的一部分。
2、元對象系統的功能
元對象系統除了提供信號槽機制在對象間進行通訊的功能,還提供了如下功能:
QObject::metaObject() 方法
獲得與一個類相關聯的 meta-object
QMetaObject::className() 方法
在運行期間返回一個對象的類名,不需要本地’C++'編譯器的RTTI(run-time type information)支持
QObject::inherits() 方法
用來判斷生成一個對象類是不是從一個特定的類繼承出來,必須是在QObject類的直接或者間接派生類當中。
QObject::tr() and QObject::trUtf8()
為軟件的國際化翻譯字符串
QObject::setProperty() and QObject::property()
根據屬性名動態的設置和獲取屬性值
使用qobject_cast()方法在QObject類之間提供動態轉換,qobject_cast()方法的功能類似于標準C++的dynamic_cast(),但qobject_cast()不需要RTTI的支持。
3、Q_PROPERTY()的使用
#define Q_PROPERTY(text)
Q_PROPERTY定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC處理。
Q_PROPERTY(type name
READ getFunction
[WRITE setFunction]
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
Type:屬性的類型
Name:屬性的名稱
READ getFunction:屬性的訪問函數
WRITE setFunction:屬性的設置函數
RESET resetFunction:屬性的復位函數
NOTIFY notifySignal:屬性發生變化的地方發射的notifySignal信號
REVISION int:屬性的版本,屬性暴露到QML中
DESIGNABLE bool:屬性在GUI設計器中是否可見,默認為true
SCRIPTABLE bool:屬性是否可以被腳本引擎訪問,默認為true
STORED bool:
USER bool:
CONSTANT:標識屬性的值是常量,值為常量的屬性沒有WRITE、NOTIFY
FINAL:標識屬性不會被派生類覆寫
注意:NOTIFY notifySignal聲明了屬性發生變化時發射notifySignal信號,但并沒有實現,因此程序員需要在屬性發生變化的地方發射notifySignal信號。
Object.h:
#ifndef OBJECT_H
#define OBJECT_H
#include <QObject>
#include <QString>
#include <QDebug>
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged)
Q_CLASSINFO("Author", "Scorpio")
Q_CLASSINFO("Version", "1.0")
Q_ENUMS(Level)
protected:
QString m_name;
QString m_level;
int m_age;
int m_score;
public:
enum Level
{
Basic,
Middle,
Advanced
};
public:
explicit Object(QString name, QObject *parent=0):QObject(parent)
{
m_name=name;
setObjectName(m_name);
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
}
int age()const
{
return m_age;
}
void setAge(const int& age)
{
m_age=age;
emit ageChanged(m_age);
}
int score()const
{
return m_score;
}
void setScore(const int& score)
{
m_score=score;
emit scoreChanged(m_score);
}
signals:
void ageChanged(int age);
void scoreChanged(int score);
public slots:
void onAgeChanged(int age)
{
qDebug() << "age changed:" << age;
}
void onScoreChanged(int score)
{
qDebug() << "score changed:" << score;
}
};
#endif // OBJECT_H
Main.cpp:
#include <QCoreApplication>
#include "Object.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object ob("object");
//設置屬性age
ob.setProperty("age", QVariant(30));
qDebug() << "age: " << ob.age();
qDebug() << "property age: " << ob.property("age").toInt();
//設置屬性score
ob.setProperty("score", QVariant(90));
qDebug() << "score: " << ob.score();
qDebug() << "property score: " << ob.property("score").toInt();
//內省intropection,運行時查詢對象信息
qDebug() << "object name: " << ob.objectName();
qDebug() << "class name: " << ob.metaObject()->className();
qDebug() << "isWidgetType: " << ob.isWidgetType();
qDebug() << "inherit: " << ob.inherits("QObject");
return a.exec();
}
4、Q_INVOKABLE使用
#define Q_INVOKABLE
Q_INVOKABLE定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC識別。
Q_INVOKABLE宏用于定義一個成員函數可以被元對象系統調用,Q_INVOKABLE宏必須寫在函數的返回類型之前。如下:
【領QT開發教程學習資料,點擊下方鏈接莬費領取↓↓,先碼住不迷路~】
點擊→領取「鏈接」
Q_INVOKABLE void invokableMethod();
invokableMethod()函數使用了Q_INVOKABLE宏聲明,invokableMethod()函數會被注冊到元對象系統中,可以使用 QMetaObject::invokeMethod()調用。
Q_INVOKABLE與QMetaObject::invokeMethod均由元對象系統喚起,在Qt C++/QML混合編程、跨線程編程、Qt Service Framework以及 Qt/ HTML5混合編程以及里廣泛使用。
A、在跨線程編程中的使用
如何調用駐足在其他線程里的QObject方法呢?Qt提供了一種非常友好而且干凈的解決方案:向事件隊列post一個事件,事件的處理將以調用所感興趣的方法為主(需要線程有一個正在運行的事件循環)。而觸發機制的實現是由MOC提供的內省方法實現的。因此,只有信號、槽以及被標記成Q_INVOKABLE的方法才能夠被其它線程所觸發調用。如果不想通過跨線程的信號、槽這一方法來實現調用駐足在其他線程里的QObject方法。另一選擇就是將方法聲明為Q_INVOKABLE,并且在另一線程中用invokeMethod喚起。
B、Qt Service Framework
Qt服務框架是Qt Mobility 1.0.2版本推出的,一個服務(service)是一個獨立的組件提供給客戶端(client)定義好的操作。客戶端可以通過服務的名稱,版本號和服務的對象提供的接口來查詢服務。 查找到服務后,框架啟動服務并返回一個指針。
服務通過插件(plug-ins)來實現。為了避免客戶端依賴某個具體的庫,服務必須繼承自QObject,保證QMetaObject?系統可以用來提供動態發現和喚醒服務的能力。要使QmetaObject機制充分的工作,服務必須滿足,其所有的方法都是通過 signal、slot、property或invokable method和Q_INVOKEBLE來實現。
QServiceManager manager;
QObject *storage ;
storage=manager.loadInterface("com.nokia.qt.examples.FileStorage");
if(storage)
QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt"));
上述代碼通過service的元對象提供的invokeMethod方法,調用文件存儲對象的deleteFile() 方法。客戶端不需要知道對象的類型,因此也沒有鏈接到具體的service庫。 當然在服務端的deleteFile方法,一定要被標記為Q_INVOKEBLE,才能夠被元對象系統識別。
Qt服務框架的一個亮點是它支持跨進程通信,服務可以接受遠程進程。在服務管理器上注冊后,進程通過signal、slot、invokable method和property來通信,就像本地對象一樣。服務可以設定為在客戶端間共享,或針對一個客戶端。 在Qt服務框架推出之前,信號、槽以及invokable method僅支持跨線程。 下圖是跨進程的服務/客戶段通信示意圖。invokable method和Q_INVOKEBLE 是跨進城、跨線程對象之間通信的重要利器。
1、Q_OBJECT宏的定義
任何從QObject派生的類都包含自己的元數據模型,一般通過宏Q_OBJECT定義。
Q_OBJECT定義在/src/corelib/kernel/Qobjectdefs.h文件中。
#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
static const QMetaObject staticMetaObject; \
Q_OBJECT_GETSTATICMETAOBJECT \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData; \
Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
QMetaObject類型的靜態成員變量staticMetaObject是元數據的數據結構。metaObject,qt_metacast,qt_metacall、qt_static_metacall四個虛函數由MOC在生成的moc_xxx.cpp文件中實現。metaObject的作用是得到元數據表指針;qt_metacast的作用是根據簽名得到相關結構的指針,返回void*指針;qt_metacall的作用是查表然后調用調用相關的函數;qt_static_metacall的作用是調用元方法(信號和槽)。
#define Q_DECL_HIDDEN __attribute__((visibility("hidden")))
2、QMetaObject類型
QMetaObject類定義在/src/corelib/kernel/Qobjectdefs.h文件。
struct Q_CORE_EXPORT QMetaObject
{
...
enum Call {
InvokeMetaMethod,
ReadProperty,
WriteProperty,
ResetProperty,
QueryPropertyDesignable,
QueryPropertyScriptable,
QueryPropertyStored,
QueryPropertyEditable,
QueryPropertyUser,
CreateInstance
};
int static_metacall(Call, int, void **) const;
static int metacall(QObject *, Call, int, void **);
struct { // private data
const QMetaObject *superdata;
const char *stringdata;
const uint *data;
const void *extradata;
} d;
};
QMetaObject中有一個嵌套結構封裝了所有的數據:
const QMetaObject *superdata;//元數據代表的類的基類的元數據
const char *stringdata;//元數據的簽名標記
const uint *data;//元數據的索引數組的指針
const QMetaObject **extradata;//擴展元數據表的指針,指向QMetaObjectExtraData數據結構。
struct QMetaObjectExtraData
{
#ifdef Q_NO_DATA_RELOCATION
const QMetaObjectAccessor *objects;
#else
const QMetaObject **objects;
#endif
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **); //from revision 6
//typedef int (*StaticMetaCall)(QMetaObject::Call, int, void **); //used from revison 2 until revison 5
StaticMetacallFunction static_metacall;
};
static_metacall是一個指向Object::qt_static_metacall 的函數指針。
3、QT_TR_FUNCTIONS宏定義
宏QT_TR_FUNCTIONS是和翻譯相關的。
#define QT_TR_FUNCTIONS \
static inline QString tr(const char *s, const char *c=0) \
{ return staticMetaObject.tr(s, c); } \
#endif
4、Qt中其它宏的定義
Qt在/src/corelib/kernel/Qobjectdefs.h文件中定義了大量的宏。
#ifndef Q_MOC_RUN
# if defined(QT_NO_KEYWORDS)
# define QT_NO_EMIT
# else
# define slots
# define signals protected
# endif
# define Q_SLOTS
# define Q_SIGNALS protected
# define Q_PRIVATE_SLOT(d, signature)
# define Q_EMIT
#ifndef QT_NO_EMIT
# define emit
#endif
#define Q_CLASSINFO(name, value)
#define Q_INTERFACES(x)
#define Q_PROPERTY(text)
#define Q_PRIVATE_PROPERTY(d, text)
#define Q_REVISION(v)
#define Q_OVERRIDE(text)
#define Q_ENUMS(x)
#define Q_FLAGS(x)
#define Q_SCRIPTABLE
#define Q_INVOKABLE
#define Q_SIGNAL
#define Q_SLOT
Qt中的大部分宏都無實際的定義,都是提供給MOC識別處理的,MOC工具通過對類中宏的解析處理生成moc_xxx.cpp文件。
在 Qt4 及之前的版本中,signals被展開成protected。Qt5則變成public,用以支持新的語法。
1、MOC功能
A、處理Q_OBJECT宏和signals/slots關鍵字,生成信號和槽的底層代碼
B、處理Q_PROPERTY()和Q_ENUM()生成property系統代碼
C、處理Q_FLAGS()和Q_CLASSINFO()生成額外的類meta信息
D、不需要MOC處理的代碼可以用預定義的宏括起來,如下:
#ifndef Q_MOC_RUN
…
#endif
2、MOC限制
A、模板類不能使用信號/槽機制
B、MOC不擴展宏,所以信號和槽的定義不能使用宏, 包括connect的時候也不能用宏做信號和槽的名字以及參數
C、從多個類派生時,QObject派生類必須放在第一個。 QObject(或其子類)作為多重繼承的父類之一時,需要把它放在第一個。 如果使用多重繼承,moc在處理時假設首先繼承的類是QObject的一個子類,需要確保首先繼承的類是QObject或其子類。
D、函數指針不能作為信號或槽的參數, 因為其格式比較復雜,MOC不能處理。可以用typedef把它定義成簡單的形式再使用。
E、用枚舉類型或typedef的類型做信號和槽的參數時,必須fully qualified。這個詞中文不知道怎么翻譯才合適,簡單的說就是, 如果是在類里定義的, 必須把類的路徑或者命名空間的路徑都加上, 防止出現混淆。如Qt::Alignment之類的,前面的Qt就是Alignment的qualifier, 必須加上,而且有幾級加幾級。
F、信號和槽不能返回引用類型
G、signals和slots關鍵字區域只能放置信號和槽的定義,不能放其它的如變量、構造函數的定義等,友元聲明不能位于信號或者槽聲明區內。
H、嵌套類不能含有信號和槽
MOC無法處理嵌套類中的信號和槽,錯誤的例子:
class A:public QObject
{
Q_OBJECT
public:
class B
{
public slots://錯誤用法
};
};
I、信號槽不能有缺省參數
3、自定義類型的注冊
Qt線程間傳遞自定義類型數據時,自己定義的類型如果直接使用信號槽來傳遞的話會產生下面這種錯誤:
原因:當一個signal被放到隊列中(queued)時,參數(arguments)也會被一起一起放到隊列中,參數在被傳送到slot之前需要被拷貝、存儲在隊列中;為了能夠在隊列中存儲參數(argument),Qt需要去construct、destruct、copy參數對象,而為了讓Qt知道怎樣去作這些事情,參數的類型需要使用qRegisterMetaType來注冊。
步驟:(以自定義XXXXX類型為例)
A、自定義類型時在類的頂部包含:#include <QMetaType>
B、在類型定義完成后,加入聲明:Q_DECLARE_METATYPE(XXXXX);
C、在main()函數中注冊自定義類類型:qRegisterMetaType<XXXXX>(“XXXXX”);
如果希望使用類型的引用,同樣要注冊:qRegisterMetaType<XXXXX>(“XXXXX&”);
4、MOC的使用
查看工程的Makefile文件可以查找到MOC生成moc_xxx.cpp文件的命令:
moc_Object.cpp: ../moc/Object.h
/usr/local/Trolltech/Qt-4.8.6/bin/moc $(DEFINES) $(INCPATH) ../moc/Object.h -o moc_Object.cpp
因此命令行可以簡化為:
moc Object.h -o moc_Object.cpp
*請認真填寫需求信息,我們會在24小時內與您取得聯系。