整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Qt开发-操控Web小车案例

Qt开发-操控Web小车案例

这次讨论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与Web嵌套

MiniBrowser

这里以Qt官方的例子MiniBrowser来说明吧。

打开方式如下:

运行效果如下:

这个例子是在Qml中嵌套了WebView。

半透明测试

涛哥做了一个简单的半透明测试。

增加了两个半透明的小方块,蓝色的在WebView上面,红色的在WebView下面。

运行效果也是正确的:

代码是这样的:


红色框中是我增加的代码。

为什么要做半透明测试呢?根据以往的经验,不同渲染方式的两种窗口/组件嵌套在一起,总会出现透明失效之类的问题,例如 qml与Widget嵌套。

渲染原理

涛哥翻了一下Qt源码,了解到渲染的实现方式,Windows平台大致如下:

chromium在单独的进程处理html渲染,并将渲染结果存储在共享内存中;主窗口在需要重绘的时候,从共享内存中获取内容并渲染。

小结

这里的WebView内部封装好了WebEngine,其本身也是一个Item,就和普通的Qml一样,属性绑定、js function都可以正常使用,暂时不深入讨论了。

Qt与Web分离

Qt与Web分离,就是字面意思,Web在单独的浏览器或者App中运行,不和Qt堆在一起。两者通过socket进行通信。

这里用我自己做的例子来说明吧。

先看看效果:

左边是Qt实现的一个简易小车,可以前进和转向。右边是Html5实现的控制端,控制左边的小车。

源码在github上: https://github.com/jaredtao/QtWeb

Qt小车

原版小车

小车来自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/)

改进小车

我做了一些修改,主要如下:

  • 去掉了DBus
  • 增加控制按钮
  • 增加WebChannel
  • 修改Car的实现,导出一些属性和函数。
  • 注册Car到WebChannel

这里贴一些关键代码

Car的头文件:

其中要说明的是:

speed和angle属性具备 读、写、change信号。

还有加速、减速、左转、右转四个公开的槽函数。

必要的知识

WebSocket和 QWebSocket

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

Qt为我们封装好了WebSocket,即QWebSocket和QWebSocketServer,简单易用。

如果你了解socket编程,就看作TCP好了;如果不了解,请先去补充一下知识吧。

WebChannel

按涛哥的理解,WebChannel是在socket上建立的一种通信协议,这个协议的作用是把QObject暴露给远端的HTML。

大致使用流程:

  1. Qt程序中,要暴露的QObject全部注册到WebChannel。
  2. Qt程序中,启动一个WebSocketServer,等待Html的连接。
  3. Html加载好qwebchannel.js文件, 然后去连接WebSocket。
  4. 连接建立以后,Qt程序中,由WebChannel接手这个WebSocket,按协议将QObject的各种“元数据”传输给远端Html。
  5. Html端,qwebchannel.js处理WebSocket收到的各种“元数据”,用js的Object 动态创建出对应的QObject。
  6. 到这里两边算是做好了准备,可以互相调用了。
  7. Qt端QObject数据变化只要发出信号,就会由WebChannel自动通知Web端;
  8. Web端可以主动调用QObject的public的 invok函数、槽函数,以及读、写属性。

Qt启动系统浏览器

在使用WebChannel的时候,Qt端建立了WebSocketServer,之后要把server的路径(例如:ws://127.0.0.1:12345)告诉Html。

一般就是在打开Html的时候带上Query参数,例如: F:\QtWeb\index.html?webChannelBaseUrl=ws://127.0.0.1:12345

Qt的OpenUrl

Qml中有 Qt.openUrlExternally, C++ 中有 QDesktopServices::openUrl,本质一样, 都可以打开一个本地的html网页。

其在Windows平台的底层实现是Win32 API。这里有个Win32 API的缺陷,传Query参数会被丢掉。

C# .net的 Process::Start

涛哥找到了替代的方案:

.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常规流程开发。

Web部分的源码也在前文提到的github仓库,子路径是QtWeb\WebChannelCar\Web

如下是Web部分的目录结构:

脚本用typescript,包管理用npm,打包用webpack,编辑器用vs code, 都中规中矩。

内容比较简单,暂时不需要前端框架,手(复)写(制)的html和css。

Html

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,这里就不贴了。

TypeScript

脚本部分需要细说了。

src文件夹为全部脚本,目录结构如下:

TypeScript中的QObject

从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.

TypeScript中连接websocket

接下来看一下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。

TypeScript中的QWebChannel

观察仔细的同学会发现,src文件夹下面,没有叫‘qwebchannel.ts’的文件,而是‘qwebchannel.js’,和一个‘qwebchannel.d.ts’

这涉及到另一个话题:

TypeScript中使用javaScript

‘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。

这一块不细说了,感兴趣的同学可以自己去研究源码。

改进qwebchannel.js以支持await

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加进去,也不需要再额外封装了。

QObject to Typescript

我们在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语言开源项目

1. Webbench

Webbench是一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力。Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行。

2. Tinyhttpd

tinyhttpd是一个超轻量型Http Server,使用C语言开发,全部代码只有502行(包括注释),附带一个简单的Client,可以通过阅读这段代码理解一个 Http Server 的本质。

3. cJSON

好玩,有趣,专业C/C++学习交流,源码下载 群:747821062

cJSON是C语言中的一个JSON编解码器,非常轻量级,C文件只有500多行,速度也非常理想。

cJSON也存在几个弱点,虽然功能不是非常强大,但cJSON的小身板和速度是最值得赞赏的。其代码被非常好地维护着,结构也简单易懂,可以作为一个非常好的C语言项目进行学习。

4. CMockery

cmockery是google发布的用于C单元测试的一个轻量级的框架。它很小巧,对其他开源包没有依赖,对被测试代码侵入性小。cmockery的源代码行数不到3K,你阅读一下will_return和mock的源代码就一目了然了。

主要特点:

  • 免费且开源,google提供技术支持;
  • 轻量级的框架,使测试更加快速简单;
  • 避免使用复杂的编译器特性,对老版本的编译器来讲,兼容性好;
  • 并不强制要求待测代码必须依赖C99标准,这一特性对许多嵌入式系统的开发很有用

5. Libev

libev是一个开源的事件驱动库,基于epoll,kqueue等OS提供的基础设施。其以高效出名,它可以将IO事件,定时器,和信号统一起来,统一放在事件处理这一套框架下处理。基于Reactor模式,效率较高,并且代码精简(4.15版本8000多行),是学习事件驱动编程的很好的资源。

6. Memcached

Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态数据库驱动网站的速度。Memcached 基于一个存储键/值对的 hashmap。Memcached-1.4.7的代码量还是可以接受的,只有10K行左右。

下载地址:http://memcached.org/

7. Lua

Lua很棒,Lua是巴西人发明的,这些都令我不爽,但是还不至于脸红,最多眼红。

让我脸红的是Lua的源代码,百分之一百的ANSI C,一点都不掺杂。在任何支持ANSI C编译器的平台上都可以轻松编译通过。我试过,真是一点废话都没有。Lua的代码数量足够小,5.1.4仅仅1.5W行,去掉空白行和注释估计能到1W行。

8. SQLite

好玩,有趣,专业C/C++学习交流,源码下载 群:747821062

SQLite是一个开源的嵌入式关系数据库,实现自包容、零配置、支持事务的SQL数据库引擎。 其特点是高度便携、使用方便、结构紧凑、高效、可靠。足够小,大致3万行C代码,250K。

9. UNIX v6

好玩,有趣,专业C/C++学习交流,源码下载 群:747821062

UNIX V6 的内核源代码包括设备驱动程序在内 约有1 万行,这个数量的源代码,初学者是能够充分理解的。有一种说法是一个人所能理解的代码量上限为1 万行,UNIX V6的内核源代码从数量上看正好在这个范围之内。看到这里,大家是不是也有“如果只有1万行的话没准儿我也能学会”的想法呢?

另一方面,最近的操作系统,例如Linux 最新版的内核源代码据说超过了1000 万行。就算不是初学者,想完全理解全部代码基本上也是不可能的。

10. NETBSD

NetBSD是一个免费的,具有高度移植性的 UNIX-like 操作系统,是现行可移植平台最多的操作系统,可以在许多平台上执行,从 64bit alpha 服务器到手持设备和嵌入式设备。NetBSD计划的口号是:”Of course it runs NetBSD”。它设计简洁,代码规范,拥有众多先进特性,使得它在业界和学术界广受好评。由于简洁的设计和先进的特征,使得它在生产和研究方面,都有卓越的表现,而且它也有受使用者支持的完整的源代码。许多程序都可以很容易地通过NetBSD Packages Collection获得。

C++ 资源大全

关于 C++ 框架、库和资源的一些汇总列表,内容包括:标准库、Web应用框架、人工智能、数据库、图片处理、机器学习、日志、代码分析等。

标准库

C++标准库,包括了STL容器,算法和函数等。

  • C++ Standard Library:是一系列类和函数的集合,使用核心语言编写,也是C++ISO自身标准的一部分。
  • Standard Template Library:标准模板库
  • C POSIX library : POSIX系统的C标准库规范
  • ISO C++ Standards Committee :C++标准委员会

框架

C++通用框架和库

  • Apache C++ Standard Library:是一系列算法,容器,迭代器和其他基本组件的集合
  • ASL :Adobe源代码库提供了同行的评审和可移植的C++源代码库。
  • Boost :大量通用C++库的集合。
  • BDE :来自于彭博资讯实验室的开发环境。
  • Cinder:提供专业品质创造性编码的开源开发社区。
  • Cxxomfort:轻量级的,只包含头文件的库,将C++ 11的一些新特性移植到C++03中。
  • Dlib:使用契约式编程和现代C++科技设计的通用的跨平台的C++库。
  • EASTL :EA-STL公共部分
  • ffead-cpp :企业应用程序开发框架
  • Folly:由Facebook开发和使用的开源C++库
  • JUCE :包罗万象的C++类库,用于开发跨平台软件
  • libPhenom:用于构建高性能和高度可扩展性系统的事件框架。
  • LibSourcey :用于实时的视频流和高性能网络应用程序的C++11 evented IO
  • LibU : C语言写的多平台工具库
  • Loki :C++库的设计,包括常见的设计模式和习语的实现。
  • MiLi :只含头文件的小型C++库
  • openFrameworks :开发C++工具包,用于创意性编码。
  • Qt :跨平台的应用程序和用户界面框架
  • Reason :跨平台的框架,使开发者能够更容易地使用Java,.Net和Python,同时也满足了他们对C++性能和优势的需求。
  • ROOT :具备所有功能的一系列面向对象的框架,能够非常高效地处理和分析大量的数据,为欧洲原子能研究机构所用。
  • STLport:是STL具有代表性的版本
  • STXXL:用于额外的大型数据集的标准模板库。
  • Ultimate++ :C++跨平台快速应用程序开发框架
  • Windows Template Library:用于开发Windows应用程序和UI组件的C++库
  • Yomm11 :C++11的开放multi-methods.

人工智能

  • btsk :游戏行为树启动器工具
  • Evolving Objects:基于模板的,ANSI C++演化计算库,能够帮助你非常快速地编写出自己的随机优化算法。
  • Neu:C++11框架,编程语言集,用于创建人工智能应用程序的多用途软件系统。

异步事件循环

  • Boost.Asio:用于网络和底层I/O编程的跨平台的C++库。
  • libev :功能齐全,高性能的时间循环,轻微地仿效libevent,但是不再像libevent一样有局限性,也修复了它的一些bug。
  • libevent :事件通知库
  • libuv :跨平台异步I/O。

音频

音频,声音,音乐,数字化音乐库

  • FMOD :易于使用的跨平台的音频引擎和音频内容的游戏创作工具。
  • Maximilian :C++音频和音乐数字信号处理库
  • OpenAL :开源音频库—跨平台的音频API
  • Opus:一个完全开放的,免版税的,高度通用的音频编解码器
  • Speex:免费编解码器,为Opus所废弃
  • Tonic: C++易用和高效的音频合成
  • Vorbis: Ogg Vorbis是一种完全开放的,非专有的,免版税的通用压缩音频格式。

生态学

生物信息,基因组学和生物技术

  • libsequence:用于表示和分析群体遗传学数据的C++库。
  • SeqAn:专注于生物数据序列分析的算法和数据结构。
  • Vcflib :用于解析和处理VCF文件的C++库
  • Wham:直接把联想测试应用到BAM文件的基因结构变异。

压缩

压缩和归档库

  • bzip2:一个完全免费,免费专利和高质量的数据压缩
  • doboz:能够快速解压缩的压缩库
  • PhysicsFS:对各种归档提供抽象访问的库,主要用于视频游戏,设计灵感部分来自于Quake3的文件子系统。
  • KArchive:用于创建,读写和操作文件档案(例如zip和 tar)的库,它通过QIODevice的一系列子类,使用gzip格式,提供了透明的压缩和解压缩的数据。
  • LZ4 :非常快速的压缩算法
  • LZHAM :无损压缩数据库,压缩比率跟LZMA接近,但是解压缩速度却要快得多。
  • LZMA :7z格式默认和通用的压缩方法。
  • LZMAT :及其快速的实时无损数据压缩库
  • miniz:单一的C源文件,紧缩/膨胀压缩库,使用zlib兼容API,ZIP归档读写,PNG写方式。
  • Minizip:Zlib最新bug修复,支持PKWARE磁盘跨越,AES加密和IO缓冲。
  • Snappy :快速压缩和解压缩
  • ZLib :非常紧凑的数据流压缩库
  • ZZIPlib:提供ZIP归档的读权限。

并发性

并发执行和多线程

  • Boost.Compute :用于OpenCL的C++GPU计算库
  • Bolt :针对GPU进行优化的C++模板库
  • C++React :用于C++11的反应性编程库
  • Intel TBB :Intel线程构件块
  • Libclsph:基于OpenCL的GPU加速SPH流体仿真库
  • OpenCL :并行编程的异构系统的开放标准
  • OpenMP:OpenMP API
  • Thrust :类似于C++标准模板库的并行算法库
  • HPX :用于任何规模的并行和分布式应用程序的通用C++运行时系统
  • VexCL :用于OpenCL/CUDA 的C++向量表达式模板库。

容器

  • C++ B-tree :基于B树数据结构,实现命令内存容器的模板库
  • Hashmaps: C++中开放寻址哈希表算法的实现

密码学

  • Bcrypt :一个跨平台的文件加密工具,加密文件可以移植到所有可支持的操作系统和处理器中。
  • BeeCrypt:
  • Botan: C++加密库
  • Crypto++:一个有关加密方案的免费的C++库
  • GnuPG: OpenPGP标准的完整实现
  • GnuTLS :实现了SSL,TLS和DTLS协议的安全通信库
  • Libgcrypt
  • libmcrypt
  • LibreSSL:免费的SSL/TLS协议,属于2014 OpenSSL的一个分支
  • LibTomCrypt:一个非常全面的,模块化的,可移植的加密工具
  • libsodium:基于NaCI的加密库,固执己见,容易使用
  • Nettle 底层的加密库
  • OpenSSL : 一个强大的,商用的,功能齐全的,开放源代码的加密库。
  • Tiny AES128 in C :用C实现的一个小巧,可移植的实现了AES128ESB的加密算法

数据库

数据库,SQL服务器,ODBC驱动程序和工具

  • hiberlite :用于Sqlite3的C++对象关系映射
  • Hiredis: 用于Redis数据库的很简单的C客户端库
  • LevelDB: 快速键值存储库
  • LMDB:符合数据库四大基本元素的嵌入键值存储
  • MySQL++:封装了MySql的C API的C++ 包装器
  • RocksDB:来自Facebook的嵌入键值的快速存储
  • SQLite:一个完全嵌入式的,功能齐全的关系数据库,只有几百KB,可以正确包含到你的项目中。

调试

调试库, 内存和资源泄露检测,单元测试

  • Boost.Test:Boost测试库
  • Catch:一个很时尚的,C++原生的框架,只包含头文件,用于单元测试,测试驱动开发和行为驱动开发。
  • CppUnit:由JUnit移植过来的C++测试框架
  • CTest:CMake测试驱动程序
  • googletest:谷歌C++测试框架
  • ig-debugheap:用于跟踪内存错误的多平台调试堆
  • libtap:用C语言编写测试
  • MemTrack —用于C++跟踪内存分配
  • microprofile- 跨平台的网络试图分析器
  • minUnit :使用C写的迷你单元测试框架,只使用了两个宏
  • Remotery:用于web视图的单一C文件分析器
  • UnitTest++:轻量级的C++单元测试框架

游戏引擎

  • Cocos2d-x :一个跨平台框架,用于构建2D游戏,互动图书,演示和其他图形应用程序。
  • Grit :社区项目,用于构建一个免费的游戏引擎,实现开放的世界3D游戏。
  • Irrlicht :C++语言编写的开源高性能的实时#D引擎
  • Polycode:C++实现的用于创建游戏的开源框架(与Lua绑定)。

图形用户界面

  • CEGUI : 很灵活的跨平台GUI库
  • FLTK :快速,轻量级的跨平台的C++GUI工具包。
  • GTK+: 用于创建图形用户界面的跨平台工具包
  • gtkmm :用于受欢迎的GUI库GTK+的官方C++接口。
  • imgui:拥有最小依赖关系的立即模式图形用户界面
  • libRocket :libRocket 是一个C++ HTML/CSS 游戏接口中间件
  • MyGUI :快速,灵活,简单的GUI
  • Ncurses:终端用户界面
  • QCustomPlot :没有更多依赖关系的Qt绘图控件
  • Qwt :用户与技术应用的Qt 控件
  • QwtPlot3D :功能丰富的基于Qt/OpenGL的C++编程库,本质上提供了一群3D控件
  • OtterUI :OtterUI 是用于嵌入式系统和互动娱乐软件的用户界面开发解决方案
  • PDCurses 包含源代码和预编译库的公共图形函数库
  • wxWidgets C++库,允许开发人员使用一个代码库可以为widows, Mac OS X,Linux和其他平台创建应用程序

图形

  • bgfx:跨平台的渲染库
  • Cairo:支持多种输出设备的2D图形库
  • Horde3D 一个小型的3D渲染和动画引擎
  • magnum C++11和OpenGL 2D/3D 图形引擎
  • Ogre 3D 用C++编写的一个面向场景,实时,灵活的3D渲染引擎(并非游戏引擎)
  • OpenSceneGraph 具有高性能的开源3D图形工具包
  • Panda3D 用于3D渲染和游戏开发的框架,用Python和C++编写。
  • Skia 用于绘制文字,图形和图像的完整的2D图形库
  • urho3d 跨平台的渲染和游戏引擎。

图像处理

  • Boost.GIL:通用图像库
  • CImg :用于图像处理的小型开源C++工具包
  • CxImage :用于加载,保存,显示和转换的图像处理和转换库,可以处理的图片格式包括 BMP, JPEG, GIF, PNG, TIFF, MNG, ICO, PCX, TGA, WMF, WBMP, JBG, J2K。
  • FreeImage :开源库,支持现在多媒体应用所需的通用图片格式和其他格式。
  • GDCM:Grassroots DICOM 库
  • ITK:跨平台的开源图像分析系统
  • Magick++:ImageMagick程序的C++接口
  • MagickWnd:ImageMagick程序的C++接口
  • OpenCV : 开源计算机视觉类库
  • tesseract-ocr:OCR引擎
  • VIGRA :用于图像分析通用C++计算机视觉库
  • VTK :用于3D计算机图形学,图像处理和可视化的开源免费软件系统。

国际化

  • gettext :GNU `gettext’
  • IBM ICU:提供Unicode 和全球化支持的C、C++ 和Java库
  • libiconv :用于不同字符编码之间的编码转换库

Jason

  • frozen : C/C++的Jason解析生成器
  • Jansson :进行编解码和处理Jason数据的C语言库
  • jbson :C++14中构建和迭代BSON data,和Json 文档的库
  • JeayeSON:非常健全的C++ JSON库,只包含头文件
  • JSON++ : C++ JSON 解析器
  • json-parser:用可移植的ANSI C编写的JSON解析器,占用内存非常少
  • json11 :一个迷你的C++11 JSON库
  • jute :非常简单的C++ JSON解析器
  • ibjson:C语言中的JSON解析和打印库,很容易和任何模型集成。
  • libjson:轻量级的JSON库
  • PicoJSON:C++中JSON解析序列化,只包含头文件
  • qt-json :用于JSON数据和 QVariant层次间的相互解析的简单类
  • QJson:将JSON数据映射到QVariant对象的基于Qt的库
  • RapidJSON: 用于C++的快速JSON 解析生成器,包含SAX和DOM两种风格的API
  • YAJL :C语言中快速流JSON解析库

日志

  • Boost.Log :设计非常模块化,并且具有扩展性
  • easyloggingpp:C++日志库,只包含单一的头文件。
  • Log4cpp :一系列C++类库,灵活添加日志到文件,系统日志,IDSA和其他地方。
  • templog:轻量级C++库,可以添加日志到你的C++应用程序中

机器学习

  • Caffe :快速的神经网络框架
  • CCV :以C语言为核心的现代计算机视觉库
  • mlpack :可扩展的C++机器学习库
  • OpenCV:开源计算机视觉库
  • Recommender:使用协同过滤进行产品推荐/建议的C语言库。
  • SHOGUN:Shogun 机器学习工具
  • sofia-ml :用于机器学习的快速增量算法套件

数学

  • Armadillo :高质量的C++线性代数库,速度和易用性做到了很好的平衡。语法和MatlAB很相似
  • blaze:高性能的C++数学库,用于密集和稀疏算法。
  • ceres-solver :来自谷歌的C++库,用于建模和解决大型复杂非线性最小平方问题。
  • CGal: 高效,可靠的集合算法集合
  • cml :用于游戏和图形的免费C++数学库
  • Eigen :高级C++模板头文件库,包括线性代数,矩阵,向量操作,数值解决和其他相关的算法。
  • GMTL:数学图形模板库是一组广泛实现基本图形的工具。
  • GMP:用于个高精度计算的C/C++库,处理有符号整数,有理数和浮点数。

多媒体

  • GStreamer :构建媒体处理组件图形的库
  • LIVE555 Streaming Media :使用开放标准协议(RTP/RTCP, RTSP, SIP) 的多媒体流库
  • libVLC :libVLC (VLC SDK)媒体框架
  • QtAv:基于Qt和FFmpeg的多媒体播放框架,能够帮助你轻而易举地编写出一个播放器
  • SDL :简单直控媒体层
  • SFML :快速,简单的多媒体库

网络

  • ACE:C++面向对象网络变成工具包
  • Boost.Asio:用于网络和底层I/O编程的跨平台的C++库
  • Casablanca:C++ REST SDK
  • cpp-netlib:高级网络编程的开源库集合
  • Dyad.c:C语言的异步网络
  • libcurl :多协议文件传输库
  • Mongoose:非常轻量级的网络服务器
  • Muduo :用于Linux多线程服务器的C++非阻塞网络库
  • net_skeleton :C/C++的TCP 客户端/服务器库
  • nope.c :基于C语言的超轻型软件平台,用于可扩展的服务器端和网络应用。 对于C编程人员,可以考虑node.js
  • Onion :C语言HTTP服务器库,其设计为轻量级,易使用。
  • POCO:用于构建网络和基于互联网应用程序的C++类库,可以运行在桌面,服务器,移动和嵌入式系统。
  • RakNet:为游戏开发人员提供的跨平台的开源C++网络引擎。
  • Tuf o :用于Qt之上的C++构建的异步Web框架。
  • WebSocket++ :基于C++/Boost Aiso的websocket 客户端/服务器库
  • ZeroMQ :高速,模块化的异步通信库

物理学

动力学仿真引擎

  • Box2D:2D的游戏物理引擎。
  • Bullet :3D的游戏物理引擎。
  • Chipmunk :快速,轻量级的2D游戏物理库
  • LiquidFun:2D的游戏物理引擎
  • ODE :开放动力学引擎-开源,高性能库,模拟刚体动力学。
  • ofxBox2d:Box2D开源框架包装器。
  • Simbody :高性能C++多体动力学/物理库,模拟关节生物力学和机械系统,像车辆,机器人和人体骨骼。

机器人学

  • MOOS-IvP :一组开源C++模块,提供机器人平台的自主权,尤其是自主的海洋车辆。
  • MRPT:移动机器人编程工具包
  • PCL :点云库是一个独立的,大规模的开放项目,用于2D/3D图像和点云处理。
  • Robotics Library (RL): 一个独立的C++库,包括机器人动力学,运动规划和控制。
  • RobWork:一组C++库的集合,用于机器人系统的仿真和控制。
  • ROS :机器人操作系统,提供了一些库和工具帮助软件开发人员创建机器人应用程序。

科学计算

  • FFTW :用一维或者多维计算DFT的C语言库。
  • GSL:GNU科学库。

脚本

  • ChaiScript :用于C++的易于使用的嵌入式脚本语言。
  • Lua :用于配置文件和基本应用程序脚本的小型快速脚本引擎。
  • luacxx:用于创建Lua绑定的C++ 11 API
  • SWIG :一个可以让你的C++代码链接到JavaScript,Perl,PHP,Python,Tcl和Ruby的包装器/接口生成器
  • V7:嵌入式的JavaScript 引擎。
  • V8 :谷歌的快速JavaScript引擎,可以被嵌入到任何C++应用程序中。

序列化

  • Cap’n Proto :快速数据交换格式和RPC系统。
  • cereal :C++11 序列化库
  • FlatBuffers :内存高效的序列化库
  • MessagePack :C/C++的高效二进制序列化库,例如 JSON
  • protobuf :协议缓冲,谷歌的数据交换格式。
  • protobuf-c :C语言的协议缓冲实现
  • SimpleBinaryEncoding:用于低延迟应用程序的对二进制格式的应用程序信息的编码和解码。
  • Thrift :高效的跨语言IPC/RPC,用于C++,Java,Python,PHP,C#和其它多种语言中,最初由Twitter开发。注:原文有误,应该是 facebook 在2007年开发的,现在是 Apache 在维护

视频

  • libvpx :VP8/VP9编码解码SDK
  • FFmpeg :一个完整的,跨平台的解决方案,用于记录,转换视频和音频流。
  • libde265 :开放的h.265视频编解码器的实现。
  • OpenH264:开源H.364 编解码器。
  • Theora :免费开源的视频压缩格式。

虚拟机

  • CarpVM:C中有趣的VM,让我们一起来看看这个。
  • MicroPython :旨在实现单片机上Python3.x的实现
  • TinyVM:用纯粹的ANSI C编写的小型,快速,轻量级的虚拟机。

Web应用框架

  • Civetweb :提供易于使用,强大的,C/C++嵌入式Web服务器,带有可选的CGI,SSL和Lua支持。
  • CppCMS :免费高性能的Web开发框架(不是 CMS).
  • Crow :一个C++微型web框架(灵感来自于Python Flask)
  • Kore :使用C语言开发的用于web应用程序的超快速和灵活的web服务器/框架。
  • libOnion:轻量级的库,帮助你使用C编程语言创建web服务器。
  • QDjango:使用C++编写的,基于Qt库的web框架,试图效仿Django API,因此得此名。
  • Wt :开发Web应用的C++库。

XML

XML就是个垃圾,xml的解析很烦人,对于计算机它也是个灾难。这种糟糕的东西完全没有存在的理由了。-Linus Torvalds

  • Expat :用C语言编写的xml解析库
  • Libxml2 :Gnome的xml C解析器和工具包
  • libxml++ :C++的xml解析器
  • PugiXML :用于C++的,支持XPath的轻量级,简单快速的XML解析器。
  • RapidXml :试图创建最快速的XML解析器,同时保持易用性,可移植性和合理的W3C兼容性。
  • TinyXML :简单小型的C++XML解析器,可以很容易地集成到其它项目中。
  • TinyXML2:简单快速的C++CML解析器,可以很容易集成到其它项目中。
  • TinyXML++:TinyXML的一个全新的接口,使用了C++的许多许多优势,模板,异常和更好的异常处理。
  • Xerces-C++ :用可移植的C++的子集编写的XML验证解析器。

多项混杂

一些有用的库或者工具,但是不适合上面的分类,或者还没有分类。

  • C++ Format :C++的小型,安全和快速格式化库
  • casacore :从aips++ 派生的一系列C++核心库
  • cxx-prettyprint:用于C++容器的打印库
  • DynaPDF :易于使用的PDF生成库
  • gcc-poison :帮助开发人员禁止应用程序中的不安全的C/C++函数的简单的头文件。
  • googlemock:编写和使用C++模拟类的库
  • HTTP Parser :C的http请求/响应解析器
  • libcpuid :用于x86 CPU检测盒特征提取的小型C库
  • libevil :许可证管理器
  • libusb:允许移动访问USB设备的通用USB库
  • PCRE:正则表达式C库,灵感来自于Perl中正则表达式的功能。
  • Remote Call Framework :C++的进程间通信框架。
  • Scintilla :开源的代码编辑控件
  • Serial Communication Library :C++语言编写的跨平台,串口库。
  • SDS:C的简单动态字符串库
  • SLDR :超轻的DNS解析器
  • SLRE: 超轻的正则表达式库
  • Stage :移动机器人模拟器
  • VarTypes:C++/Qt4功能丰富,面向对象的管理变量的框架。
  • ZBar:‘条形码扫描器’库,可以扫描照片,图片和视频流中的条形码,并返回结果。
  • CppVerbalExpressions :易于使用的C++正则表达式
  • QtVerbalExpressions:基于C++ VerbalExpressions 库的Qt库
  • PHP-CPP:使用C++来构建PHP扩展的库
  • Better String :C的另一个字符串库,功能更丰富,但是没有缓冲溢出问题,还包含了一个C++包装器。

软件

用于创建开发环境的软件

编译器

C/C++编译器列表

  • Clang :由苹果公司开发的
  • GCC:GNU编译器集合
  • Intel C++ Compiler :由英特尔公司开发
  • LLVM :模块化和可重用编译器和工具链技术的集合
  • Microsoft Visual C++ :MSVC,由微软公司开发
  • Open WatCom :Watcom,C,C++和Fortran交叉编译器和工具
  • TCC :轻量级的C语言编译器

在线编译器

在线C/C++编译器列表

  • codepad :在线编译器/解释器,一个简单的协作工具
  • CodeTwist:一个简单的在线编译器/解释器,你可以粘贴的C,C++或者Java代码,在线执行并查看结果
  • coliru :在线编译器/shell, 支持各种C++编译器
  • Compiler Explorer:交互式编译器,可以进行汇编输出
  • CompileOnline:Linux上在线编译和执行C++程序
  • Ideone :一个在线编译器和调试工具,允许你在线编译源代码并执行,支持60多种编程语言。

调试器

C/C++调试器列表

  • Comparison of debuggers :来自维基百科的调试器列表
  • GDB :GNU调试器
  • Valgrind:内存调试,内存泄露检测,性能分析工具。

集成开发环境(IDE)

C/C++集成开发环境列表

  • AppCode :构建与JetBrains’ IntelliJ IDEA 平台上的用于Objective-C,C,C++,Java和Java开发的集成开发环境
  • CLion:来自JetBrains的跨平台的C/C++的集成开发环境
  • Code::Blocks :免费C,C++和Fortran的集成开发环境
  • CodeLite :另一个跨平台的免费的C/C++集成开发环境
  • Dev-C++:可移植的C/C++/C++11集成开发环境
  • Eclipse CDT:基于Eclipse平台的功能齐全的C和C++集成开发环境
  • Geany :轻量级的快速,跨平台的集成开发环境。
  • IBM VisualAge :来自IBM的家庭计算机集成开发环境。
  • Irony-mode:由libclang驱动的用于Emacs的C/C++微模式
  • KDevelop:免费开源集成开发环境
  • Microsoft Visual Studio :来自微软的集成开发环境
  • NetBeans :主要用于Java开发的的集成开发环境,也支持其他语言,尤其是PHP,C/C++和HTML5。
  • Qt Creator:跨平台的C++,Javascript和QML集成开发环境,也是Qt SDK的一部分。
  • rtags:C/C++的客户端服务器索引,用于 跟基于clang的emacs的集成
  • Xcode :由苹果公司开发
  • YouCompleteMe:一个用于Vim的根据你敲的代码快速模糊搜索并进行代码补全的引擎。

构建系统

  • Bear :用于为clang工具生成编译数据库的工具
  • Biicode:基于文件的简单依赖管理器。
  • CMake :跨平台的免费开源软件用于管理软件使用独立编译的方法进行构建的过程。
  • CPM:基于CMake和Git的C++包管理器
  • FASTBuild:高性能,开源的构建系统,支持高度可扩展性的编译,缓冲和网络分布。
  • Ninja :专注于速度的小型构建系统
  • Scons :使用Python scipt 配置的软件构建工具
  • tundra :高性能的代码构建系统,甚至对于非常大型的软件项目,也能提供最好的增量构建次数。
  • tup:基于文件的构建系统,用于后台监控变化的文件。

静态代码分析

提高质量,减少瑕疵的代码分析工具列表

  • Cppcheck :静态C/C++代码分析工具
  • include-what-you-use :使用clang进行代码分析的工具,可以#include在C和C++文件中。
  • OCLint :用于C,C++和Objective-C的静态源代码分析工具,用于提高质量,减少瑕疵。
  • Clang Static Analyzer:查找C,C++和Objective-C程序bug的源代码分析工具
  • List of tools for static code analysis :来自维基百科的静态代码分析工具列表

、Qt元对象系统简介

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 是跨进城、跨线程对象之间通信的重要利器。

二、Qt元对象系统源码解析

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,用以支持新的语法。

三、元对象编译器MOC

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