这次讨论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小时内与您取得联系。