这个话题并不是一个新的话题,网上有前辈做过详细的描述(Mixing Objective-C, C++ and Objective-C++: an Updated Summary)。之所以做为一篇文章,是因为在实际项目中用到了这种混合,加以记录。

首先需要澄清的是,Objective-C是strict superset of C。C++是基于C语言的面向对象的扩充,但不是strict superset of C。Objective-C++是Objective-C的扩展,使得Objective-C可以链接C++代码。通常情况下,要使用Objective-C++,需要将.m源文件改成.mm源文件。

其次需要澄清的是,Objective-C代码跟C++代码是不能混合使用的,也就是说,Objective-C的头文件中是看不到C++的头文件的。当然,C++的头文件是看不到Objective-C的头文件的。只有Objective-C++文件(也就是.mm文件)可以看到Objective-C的头文件和C++的头文件。

正因为Objective-C和C++互相不能见到对方的头文件,那么就意味着,现有的C++的代码库,想要在Objective-C中使用,是需要封装的,通常,这种封装是借助于Objective-C++写成的Wrapper.

另一方面,Wrapper本身也有技巧,那就是使用Objective-C的class extension特性(或者使用Pointer to implementation设计模式),避免将C++头文件暴露给Objective-C(否则无法编译通过)。具体细节可以参考文章开头的链接。

背景

在使用Qt给MacOS上的截图工具实现自动选中窗口区域需求的时候,原理无非就是获得桌面上可见的所有窗口的坐标以及尺寸,再将这些数据用一个结构体封装起来,按照在桌面的Z序进行排序,最后对鼠标的移动事件进行判断,按照Z序去对鼠标是否在某个窗口的矩形范围内进行判断,直到找到在范围内的矩形,这就是我们要自动选中的矩形了,如果将所有窗口都遍历完了还没发现鼠标正在某个矩形内的话,就不进行自动选中操作。

在Windows下我们可以很方便的使用EnumWindows这个Win32 API去获得桌面上所有窗口的坐标和尺寸,Win32 API可以由C++很方便的调用,而在MacOS上,单纯的使用C++仿佛无法获得我需要的这些数据,于是只好使用Apple的Objective-C提供的接口去获得这些数据。

通过在苹果开发者文档找到了MacOS上很多获得窗口信息的接口,使用CGWindowListCopyWindowInfo就能得到每个窗口的具体数据,接下来我们就要开始Qt与Objective-C混合编程了。

Objective-C是一种在C的基础上加入面向对象特性扩充而成的编程语言,通常称为jObC和较少用的 Objective C或ObjC。在一定程度上,可以把 Objective-C看成是ANSI版本C语言的一个超集,它支持相同的C语言基本语法,同时它还扩展了标准的 ANSI C语言的语法,包括定义类、方法和属性。当然还有其他一些结构的完善和拓展,如类别(Category)的出现。

所以Objective-c是可以直接调用C语言的,那么能否直接调用C++呢?答案是肯定的。

Objective-C源文件介绍

首先我要说一下Objective-C的源文件,后缀是.m或.mm,在.mm文件里,可以直接使用C++代码。所以,我们要混合Qt代码与Objective-C代码,就需要在Qt项目里加入mm文件。

而要混合Objective-C代码,需要更改一下pro文件,添加mm文件,如果有用到MacOS的API的话,则可能还需要添加MacOS的Framework。

添加源文件,需要在.pro中使用OBJECTIVE_SOURCES这个变量,如下所示:

OBJECTIVE_SOURCE += \
Getallvisiblewndpos.mm

添加头文件,需要在.pro中使用OBJECTIVE_HEADERS这个变量,如下所示:

OBJECTIVE_HEADERS += \
getallvisiblewndpos.h

添加Framework,需要在.pro中使用LIBS这个变量,如下所示:

LIBS += -framework CoreGraphics
LIBS += -framework CoreFoundation

混合代码

要使用MacOS提供的框架,需要在.mm文件内包含相关的头文件,又有几部分工作要做。一个是在.pro文件里加入Framework路径,使用LIBS变量即可,不多说了。另外一点是在mm文件内包含Objective-C的头文件,与C++头文件一个理儿,不过使用Objective-C的头文件要使用#import,而使用C++的头文件要使用#include,且所有的#include要写在#import的上面。

需要注意的是,凡是出现了Objective-C源代码和头文件的文件都需要将拓展名改成.mm,只有这样编译器在编译的时候才会既认识Objective-C代码,又认识C++代码。

注意事项

当遇到链接失败的问题时,如下图所示:

通常都是因为只引入了头文件而没有引入对应的Framework,通过在苹果开发者文档或者在Xcode中查找对应的Framework并添加到.pro文件中加以解决。

附赠Qt下Objective-C获得MacOS下所有窗口坐标、尺寸、Z序的方式:

//getAllVisibleWndPos.h
#ifndef GETALLVISIBLEWNDPOS_H
#define GETALLVISIBLEWNDPOS_H

#include <QRect>
#include <QList>
#include <vector>

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import <CoreGraphics/CGWindow.h>
#import <CoreFoundation/CFArray.h>

struct WND_INFO{
	int layer;//Z序
	int index;//序号
	QRect pos;
};

bool CompareWNDINFO(const WND_INFO& A , const WND_INFO& B);
QList<WND_INFO> GetAllVisibleWndPos();

#endif // GETALLVISIBLEWNDPOS_H
//getAllVisibleWndPos.mm
#import <getallvisiblewndpos.h>

bool CompareWNDINFO(const WND_INFO& A , const WND_INFO& B)
{
	if(A.layer == B.layer)
	{
		return A.index < B.index;
	}
	if(A.layer > B.layer && B.layer > 0)
	{
		return true;
	}
	if(A.layer < B.layer && A.layer > 0)
	{
		return false;
	}
	return A.layer == 0;
}

QList<WND_INFO> GetAllVisibleWndPos()
{
	std::vector<WND_INFO> vec;
	CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements , kCGNullWindowID);
	CFIndex cnt = CFArrayGetCount(windowList);
	for (CFIndex i = 0; i < cnt ; i++)
	{
		NSDictionary *dict = (NSDictionary* )CFArrayGetValueAtIndex(windowList , i);
		int layer = 0;
		CFNumberRef numberRef = (__bridge CFNumberRef) dict[@"kCGWindowLayer"];
		CFNumberGetValue(numberRef , kCFNumberSInt32Type , &layer);
		if(layer < 0)
			continue;
		CGRect windowRect;
		CGRectMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)(dict[@"kCGWindowBounds"]) , &windowRect);
		
		QRectF pos;
		pos.setRect(windowRect.origin.x , windowRect.origin.y , windowRect.size.width , windowRect.size,height);
		
		WND_INFO info;
		info.layer = layer;
		info.pos.setRect(pos.x() , pos.y() , pos.width() , pos.height());
		info.index = i;
		
		vec.push_back(info);
	}
	
	std::sort(vec.begin() , vec.end() , CompareWNDINFO);
	QList<WND_INFO> list;
	for(int i = 0 ; i < vec.size() ; i++)
	{
		list.push_back(vec[i]);
	}
	return list;
}

Windows下打包Qt应用程序

在Qt Creator下用Release编译一遍程序,生成相应的exe文件。

 在Qt Creator下编译好的Release下的exe 拷贝到一个新建的文件夹下面;在开始菜单搜索Qt,找到如图这个命令行程序(命令行程序的版本必须与你使用的编译器版本严格对应,否则打包出来的程序将无法正常运行),然后运行。

打开QT命令行,然后利用cd语句进入你拷贝的exe文件放在的文件夹下; 如:”cd C:\Users\Final\Desktop\XXX”,接着运行命令行语句:”windeployqt XXX.exe”(XXX.exe用你的exe文件名替代),然后文件夹下就会出现所有QT依赖。

然后将这个文件夹打包好就可以发布了。如果需要将所有的dll以及其他资源文件全部打包成一个exe,我们可以使用Engima Virtual Box这款软件进行打包。

MacOS下打包Qt应用程序

在Qt Creator下用Release编译一遍程序,生成相应的应用程序。

然后找到Qt安装目录下的macdeployqt,比如在我的安装目录下该文件在“/users/dxf/Qt5.9.0/clang_64/bin/”目录,其中Qt5.9.0以及clang_64分别代表Qt的版本和Qt使用的C++编译器,在使用macdeployqt的时候Qt版本以及使用的编译器必须和编译时使用的Qt库版本及编译器严格对应,否则程序运行阶段将会出现不可预知的问题。

将macdeployqt拖入终端中,然后将你的应用程序也拖入终端中,如图所示,执行相应的打包命令,我们的Qt应用程序的所有依赖的库就会被打入应用程序中了。

如果需要打包成MacOS下的dmg文件,只需在命令行后加上-dmg即可,如图所示。

在Windows下对打包的应用程序进行压缩

在对Qt应用程序进行打包之后,这时候就会看到Qt已经把需要用到的DLL都复制过来了。如果觉得这时候整个程序比较大的话,可以根据需要去掉一些东东:

  • libEGL.dll,libGLESV2.dll这两个文件是ANGLE的文件,可以去掉。opengl32sw.dll是软件模拟OpenGL使用到的,除非用户的系统连DirectX支持都不完整——虚拟机环境就是这样——不然这个文件也完全没有用。 QtWidgets/C++程序都不用OpenGL,所以直接去掉即可。可在调用 windeployqt.exe时加”–no-angle”和”–no-opengl-sw”这两个参数。
  • 如果没有使用svg的话,iconenginesqsvgicon.dll, imageformatsqsvg.dll,Qt5Svg.dll这三个文件也可以删掉。
  • 如果没有对应用程序做国际化处理的话,translations里面的翻译文件也可以删掉。
  • QML程序没有使用QtWidgets/C++,可以删掉Qt5Widgets.dll。
  • 如果imageformats目录里面有几种图像格式没用上,也可以删掉。我自己通常把整个目录都删掉,Qt已经编译了png的支持,能读写程序包含的图标就够,其它格式不重要。
  • qmltooling和Qt5Network.dll是用于QML调试用的,可以删掉。

经过以上裁剪,使用7zip压缩完以后,一个QtWidgets/C++的HelloWorld程序最终只剩下5.64M,一个 QtControls程序是5.86M, QtQuick程序还会更小一些。

通常情况下,只需要platforms\qwindows.dll、Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll四个。Qt 5拆得那么散就是想解决这个问题。所以不要打包所有的dll,用到哪个就加哪个。

关于Qt版本对应用程序大小的影响

如果你用的Qt版本为Qt 5.x甚至Qt6.x,5.x和6.x增加了很多新特性的确大了一些。
如果只是写个小程序,不需要5.x的新特性,那么推荐Qt 4.7.x/4.8.x。

Qt适合三种场景:

  • 必须跨平台项目
  • 大项目,代码行数在30万+
  • Qt铁杆粉的项目

这三种场景下,安装包大小都不是主要问题。

减小安装包的方法也很简单:自己编译Qt库。
在configure的时候去掉RTTI,异常,Qt3支持,优化选项用最小大小(性能差不了太多)。
如果你的程序真的很小,那么可以直接静态链接,这样就更小了。

MacOS下应用程序“XXX”不能打开

当MacOS下的应用程序在传输过程中有遇到过压缩的话,经常会遇到「应用程序“XXX”不能打开」的提示,这是由于应用经过压缩和解压之后,其中的可执行文件的权限被抹除,导致无法打开。下面以ScreenShot应用为例子,解决一下这个问题。

1、右击该应用,选择“显示包内容”。

2、依次打开“Content”、“MacOS”目录,找到ScreenShot文件,我们会发现这个文件变成了文本编辑文稿类型的文件,而实际上这个文件应该为Unix可执行文件,由于在压缩的过程中权限丢失,所以这个文件变成了类型不明的文件,导致应用无法打开。

3、打开终端,输入“chmod +x”,注意“chmod”和“+x”中间有个空格,然后再输入空格,将ScreenShot文件拖入终端,接着按回车键执行指令。“chmod +x ScreenShot”的意思就是给ScreenShot这个文件执行权限的意思。

4、执行完这条命令之后,ScreenShot就被成功给予了执行权限,变成了Unix执行文件,这时这个应用就可以正常运行了。

枚举值键值备注
Key_Escape0x01000000Esc键(左上角)
Key_Tab0x01000001Tab键(制表键)
Key_Backtab0x01000002Shift+Tab
Key_BackTabKey_Backtab与Key_Backtab键值相同,但在Qt3版本使用
Key_Backspace0x01000003退格(回格)
Key_BackSpaceKey_Backspace与Key_Backspace键值相同,但在Qt3版本使用
Key_Return0x01000004字母键盘回车
Key_Enter0x01000005数字键盘回车
Key_Insert0x01000006插入
Key_Delete0x01000007删除
Key_Pause0x01000008暂停中断
Key_Print0x01000009打印
Key_SysReq0x0100000a系统请求键
Key_Clear0x0100000b清除
Key_Home0x01000010光标移动到起始值
Key_End0x01000011结束
Key_Left0x01000012左键
Key_Up0x01000013上键
Key_Right0x01000014右键
Key_Down0x01000015下键
Key_PageUp0x01000016上页
Key_PriorKey_PageUp与Key_PageUp键值相同,但在Qt3版本使用
Key_PageDown0x01000017下页
Key_NextKey_PageDown与Key_PageDown键值相同,但在Qt3版本使用
Key_Shift0x01000020上档(调节器),通常用于组合按键使用
Key_Control0x01000021控制键,通常用于组合按键使用
Key_Meta0x01000022变换按键,现代计算机键盘一般无此键,此键通常用Alt键或者Windows键仿真
Key_Alt0x01000023替换键,通常用于组合按键使用
Key_CapsLock0x01000024大写锁定
Key_NumLock0x01000025数码锁定
Key_ScrollLock0x01000026滚动锁定
Key_F10x01000030功能键F1
Key_F20x01000031功能键F2
Key_F30x01000032功能键F3
Key_F40x01000033功能键F4
Key_F50x01000034功能键F5
Key_F60x01000035功能键F6
Key_F70x01000036功能键F7
Key_F80x01000037功能键F8
Key_F90x01000038功能键F9
Key_F100x01000039功能键F10
Key_F110x0100003a功能键F11
Key_F120x0100003b功能键F12
Key_F130x0100003c功能键F13
Key_F140x0100003d功能键F14
Key_F150x0100003e功能键F15
Key_F160x0100003f功能键F16
Key_F170x01000040功能键F17
Key_F180x01000041功能键F18
Key_F190x01000042功能键F19
Key_F200x01000043功能键F20
Key_F210x01000044功能键F21
Key_F220x01000045功能键F22
Key_F230x01000046功能键F23
Key_F240x01000047功能键F24
Key_F250x01000048功能键F25(X11系统独有)
Key_F260x01000049功能键F26(X11系统独有)
Key_F270x0100004a功能键F27(X11系统独有)
Key_F280x0100004b功能键F28(X11系统独有)
Key_F290x0100004c功能键F29(X11系统独有)
Key_F300x0100004d功能键F30(X11系统独有)
Key_F310x0100004e功能键F31(X11系统独有)
Key_F320x0100004f功能键F32(X11系统独有)
Key_F330x01000050功能键F33(X11系统独有)
Key_F340x01000051功能键F34(X11系统独有)
Key_F350x01000052功能键F35(X11系统独有)
Key_Super_L0x01000053左超级按键(Window系统下左边键盘徽标)
Key_Super_R0x01000054右超级按键(Window系统下右边键盘徽标)
Key_Menu0x01000055菜单
Key_Hyper_L0x01000056左Hyper按键
Key_Hyper_R0x01000057右Hyper按键
Key_Help0x01000058帮助
Key_Direction_L0x01000059左方向按键
Key_Direction_R0x01000060右方向按键
Key_Space0x20空格键(7位可打印ASCII)
Key_AnyKey_Space与Key_Space键值相同,但在Qt3版本使用
Key_Exclam0x21!
Key_QuoteDbl0x22\
Key_NumberSign0x23#
Key_Dollar0x24$
Key_Percent0x25%
Key_Ampersand0x26&
Key_Apostrophe0x27
Key_ParenLeft0x28(
Key_ParenRight0x29)
Key_Asterisk0x2a*
Key_Plus0x2b+
Key_Comma0x2c,
Key_Minus0x2d
Key_Period0x2e.
Key_Slash0x2f/
Key_00x300
Key_10x311
Key_20x322
Key_30x333
Key_40x344
Key_50x355
Key_60x366
Key_70x377
Key_80x388
Key_90x399
Key_Colon0x3a:
Key_Semicolon0x3b;
Key_Less0x3c<
Key_Equal0x3d=
Key_Greater0x3e>
Key_Question0x3f?
Key_At0x40@
Key_A0x41A
Key_B0x42B
Key_C0x43C
Key_D0x44D
Key_E0x45E
Key_F0x46F
Key_G0x47G
Key_H0x48H
Key_I0x49I
Key_J0x4aJ
Key_K0x4bK
Key_L0x4cL
Key_M0x4dM
Key_N0x4eN
Key_O0x4fO
Key_P0x50P
Key_Q0x51Q
Key_R0x52R
Key_S0x53S
Key_T0x54T
Key_U0x55U
Key_V0x56V
Key_W0x57W
Key_X0x58X
Key_Y0x59Y
Key_Z0x5aZ
Key_BracketLeft0x5b[
Key_Backslash0x5c\
Key_BracketRight0x5d]
Key_AsciiCircum0x5e^
Key_Underscore0x5f_
Key_QuoteLeft0x60`
Key_BraceLeft0x7b{
Key_Bar0x7c\
Key_BraceRight0x7d}
Key_AsciiTilde0x7e~
Key_nobreakspace0x0a0不间断空格
Key_exclamdown0x0a1
Key_cent0x0a2
Key_sterling0x0a3英国货币;标准纯银;英镑
Key_currency0x0a4货币
Key_yen0x0a5日元
Key_brokenbar0x0a6间断条(仅供参考)
Key_section0x0a7分区(仅供参考)
Key_diaeresis0x0a8分音符(仅供参考)
Key_copyright0x0a9版权(仅供参考)
Key_ordfeminine0x0aa女性(仅供参考)
Key_guillemotleft0x0ab左引号
Key_notsign0x0ac告示牌(仅供参考)
Key_hyphen0x0ad连字号(仅供参考)
Key_registered0x0ae注册(仅供参考)
Key_macron0x0af长音符号(加于元音上)(仅供参考)
Key_degree0x0b0度数(仅供参考)
Key_plusminus0x0b1加减符(仅供参考)
Key_twosuperior0x0b2两个优势(仅供参考)
Key_threesuperior0x0b3三个优势(仅供参考)
Key_acute0x0b4加剧(仅供参考)
Key_mu0x0b5希腊字母第12字(仅供参考)
Key_paragraph0x0b6段落;分段符号(仅供参考)
Key_periodcentered0x0b7有圆心的句号”.”(仅供参考)
Key_cedilla0x0b8变音符号(仅供参考)
Key_onesuperior0x0b9一个优势(仅供参考)
Key_masculine0x0ba男性(仅供参考)
Key_guillemotright0x0bb直角引号(仅供参考)
Key_onequarter0x0bc四分之一(仅供参考)
Key_onehalf0x0bd二分之一(仅供参考)
Key_threequarters0x0be四分之三(仅供参考)
Key_questiondown0x0bf下一个问题(仅供参考)
Key_Agrave0x0c0
Key_Aacute0x0c1
Key_Acircumflex0x0c2
Key_Atilde0x0c3
Key_Adiaeresis0x0c4
Key_Aring0x0c5
Key_AE0x0c6
Key_Ccedilla0x0c7
Key_Egrave0x0c8
Key_Eacute0x0c9
Key_Ecircumflex0x0ca
Key_Ediaeresis0x0cb
Key_Igrave0x0cc
Key_Iacute0x0cd
Key_Icircumflex0x0ce
Key_Idiaeresis0x0cf
Key_ETH0x0d0
Key_Ntilde0x0d1
Key_Ograve0x0d2
Key_Oacute0x0d3
Key_Ocircumflex0x0d4
Key_Otilde0x0d5
Key_Odiaeresis0x0d6
Key_multiply0x0d7
Key_Ooblique0x0d8
Key_Ugrave0x0d9
Key_Uacute0x0da
Key_Ucircumflex0x0db
Key_Udiaeresis0x0dc
Key_Yacute0x0dd
Key_THORN0x0de
Key_ssharp0x0df
Key_agraveKey_Agrave
Key_aacuteKey_Aacute
Key_acircumflexKey_Acircumflex
Key_atildeKey_Atilde
Key_adiaeresisKey_Adiaeresis
Key_aringKey_Aring
Key_aeKey_AE
Key_ccedillaKey_Ccedilla
Key_egraveKey_Egrave
Key_eacuteKey_Eacute
Key_ecircumflexKey_Ecircumflex
Key_ediaeresisKey_Ediaeresis
Key_igraveKey_Igrave
Key_iacuteKey_Iacute
Key_icircumflexKey_Icircumflex
Key_idiaeresisKey_Idiaeresis
Key_ethKey_ETH
Key_ntildeKey_Ntilde
Key_ograveKey_Ograve
Key_oacuteKey_Oacute
Key_ocircumflexKey_Ocircumflex
Key_otildeKey_Otilde
Key_odiaeresisKey_Odiaeresis
Key_division0x0f7
Key_oslashKey_Ooblique
Key_ugraveKey_Ugrave
Key_uacuteKey_Uacute
Key_ucircumflexKey_Ucircumflex
Key_udiaeresisKey_Udiaeresis
Key_yacuteKey_Yacute
Key_thornKey_THORN
Key_ydiaeresis0x0ff
// International input method support (X keycode – 0xEE00, the definition follows Qt/Embedded 2.3.7) Only interesting if you are writing your own input method. International & multi-key character composition
Key_AltGr0x01001103
Key_Multi_key0x01001120Multi-key character compose
Key_Codeinput0x01001137
Key_SingleCandidate0x0100113c
Key_MultipleCandidate0x0100113d
Key_PreviousCandidate0x0100113e
// Misc Functions
Key_Mode_switch0x0100117eCharacter set switch
// Key_script_switch = 0x0100117e, // Alias for mode_switch
// Japanese keyboard support
Key_Kanji0x01001121Kanji, Kanji convert
Key_Muhenkan0x01001122Cancel Conversion
// Key_Henkan_Mode = 0x01001123, // Start/Stop Conversion
Key_Henkan0x01001123Alias for Henkan_Mode
Key_Romaji0x01001124to Romaji
Key_Hiragana0x01001125to Hiragana
Key_Katakana0x01001126to Katakana
Key_Hiragana_Katakana0x01001127Hiragana/Katakana toggle
Key_Zenkaku0x01001128to Zenkaku
Key_Hankaku0x01001129to Hankaku
Key_Zenkaku_Hankaku0x0100112aZenkaku/Hankaku toggle
Key_Touroku0x0100112bAdd to Dictionary
Key_Massyo0x0100112cDelete from Dictionary
Key_Kana_Lock0x0100112dKana Lock
Key_Kana_Shift0x0100112eKana Shift
Key_Eisu_Shift0x0100112fAlphanumeric Shift
Key_Eisu_toggle0x01001130Alphanumeric toggle
//Key_Kanji_Bangou = 0x01001137, // Codeinput
//Key_Zen_Koho = 0x0100113d, // Multiple/All Candidate(s)
//Key_Mae_Koho = 0x0100113e, // Previous Candidate
// Korean keyboard support
// In fact, many Korean users need only 2 keys, Key_Hangul and
// Key_Hangul_Hanja. But rest of the keys are good for future.
Key_Hangul0x01001131Hangul start/stop(toggle)
Key_Hangul_Start0x01001132Hangul start
Key_Hangul_End0x01001133Hangul end, English start
Key_Hangul_Hanja0x01001134Start Hangul->Hanja Conversion
Key_Hangul_Jamo0x01001135Hangul Jamo mode
Key_Hangul_Romaja0x01001136Hangul Romaja mode
//Key_Hangul_Codeinput0x01001137, // Hangul code input mode
Key_Hangul_Jeonja0x01001138Jeonja mode
Key_Hangul_Banja0x01001139Banja mode
Key_Hangul_PreHanja0x0100113aPre Hanja conversion
Key_Hangul_PostHanja0x0100113bPost Hanja conversion
//Key_Hangul_SingleCandidate = 0x0100113c, // Single candidate
//Key_Hangul_MultipleCandidate = 0x0100113d, // Multiple candidate
//Key_Hangul_PreviousCandidate = 0x0100113e, // Previous candidate
Key_Hangul_Special0x0100113fSpecial symbols
//Key_Hangul_switch = 0x0100117e, // Alias for mode_switch
// dead keys (X keycode – 0xED00 to avoid the conflict)
Key_Dead_Grave0x01001250
Key_Dead_Acute0x01001251
Key_Dead_Circumflex0x01001252
Key_Dead_Tilde0x01001253
Key_Dead_Macron0x01001254
Key_Dead_Breve0x01001255
Key_Dead_Abovedot0x01001256
Key_Dead_Diaeresis0x01001257
Key_Dead_Abovering0x01001258
Key_Dead_Doubleacute0x01001259
Key_Dead_Caron0x0100125a
Key_Dead_Cedilla0x0100125b
Key_Dead_Ogonek0x0100125c
Key_Dead_Iota0x0100125d
Key_Dead_Voiced_Sound0x0100125e
Key_Dead_Semivoiced_Sound0x0100125f
Key_Dead_Belowdot0x01001260
Key_Dead_Hook0x01001261
Key_Dead_Horn0x01001262
// multimedia/internet keys – ignored by default – see QKeyEvent c’tor
Key_Back0x01000061
Key_Forward0x01000062
Key_Stop0x01000063
Key_Refresh0x01000064
Key_VolumeDown0x01000070
Key_VolumeMute0x01000071
Key_VolumeUp0x01000072
Key_BassBoost0x01000073
Key_BassUp0x01000074
Key_BassDown0x01000075
Key_TrebleUp0x01000076
Key_TrebleDown0x01000077
Key_MediaPlay0x01000080
Key_MediaStop0x01000081
Key_MediaPrevious0x01000082
Key_MediaPrevKey_MediaPrevious
Key_MediaNext0x01000083
Key_MediaRecord0x01000084
Key_MediaPause0x1000085
Key_MediaTogglePlayPause0x1000086
Key_HomePage0x01000090
Key_Favorites0x01000091
Key_Search0x01000092
Key_Standby0x01000093
Key_OpenUrl0x01000094
Key_LaunchMail0x010000a0
Key_LaunchMedia0x010000a1
Key_Launch00x010000a2
Key_Launch10x010000a3
Key_Launch20x010000a4
Key_Launch30x010000a5
Key_Launch40x010000a6
Key_Launch50x010000a7
Key_Launch60x010000a8
Key_Launch70x010000a9
Key_Launch80x010000aa
Key_Launch90x010000ab
Key_LaunchA0x010000ac
Key_LaunchB0x010000ad
Key_LaunchC0x010000ae
Key_LaunchD0x010000af
Key_LaunchE0x010000b0
Key_LaunchF0x010000b1
Key_MonBrightnessUp0x010000b2
Key_MonBrightnessDown0x010000b3
Key_KeyboardLightOnOff0x010000b4
Key_KeyboardBrightnessUp0x010000b5
Key_KeyboardBrightnessDown0x010000b6
Key_PowerOff0x010000b7
Key_WakeUp0x010000b8
Key_Eject0x010000b9
Key_ScreenSaver0x010000ba
Key_WWW0x010000bb
Key_Memo0x010000bc
Key_LightBulb0x010000bd
Key_Shop0x010000be
Key_History0x010000bf
Key_AddFavorite0x010000c0
Key_HotLinks0x010000c1
Key_BrightnessAdjust0x010000c2
Key_Finance0x010000c3
Key_Community0x010000c4
Key_AudioRewind0x010000c5
Key_BackForward0x010000c6
Key_ApplicationLeft0x010000c7
Key_ApplicationRight0x010000c8
Key_Book0x010000c9
Key_CD0x010000ca
Key_Calculator0x010000cb
Key_ToDoList0x010000cc
Key_ClearGrab0x010000cd
Key_Close0x010000ce
Key_Copy0x010000cf
Key_Cut0x010000d0
Key_Display0x010000d1
Key_DOS0x010000d2
Key_Documents0x010000d3
Key_Excel0x010000d4
Key_Explorer0x010000d5
Key_Game0x010000d6
Key_Go0x010000d7
Key_iTouch0x010000d8
Key_LogOff0x010000d9
Key_Market0x010000da
Key_Meeting0x010000db
Key_MenuKB0x010000dc
Key_MenuPB0x010000dd
Key_MySites0x010000de
Key_News0x010000df
Key_OfficeHome0x010000e0
Key_Option0x010000e1
Key_Paste0x010000e2
Key_Phone0x010000e3
Key_Calendar0x010000e4
Key_Reply0x010000e5
Key_Reload0x010000e6
Key_RotateWindows0x010000e7
Key_RotationPB0x010000e8
Key_RotationKB0x010000e9
Key_Save0x010000ea
Key_Send0x010000eb
Key_Spell0x010000ec
Key_SplitScreen0x010000ed
Key_Support0x010000ee
Key_TaskPane0x010000ef
Key_Terminal0x010000f0
Key_Tools0x010000f1
Key_Travel0x010000f2
Key_Video0x010000f3
Key_Word0x010000f4
Key_Xfer0x010000f5
Key_ZoomIn0x010000f6
Key_ZoomOut0x010000f7
Key_Away0x010000f8
Key_Messenger0x010000f9
Key_WebCam0x010000fa
Key_MailForward0x010000fb
Key_Pictures0x010000fc
Key_Music0x010000fd
Key_Battery0x010000fe
Key_Bluetooth0x010000ff
Key_WLAN0x01000100
Key_UWB0x01000101
Key_AudioForward0x01000102
Key_AudioRepeat0x01000103
Key_AudioRandomPlay0x01000104
Key_Subtitle0x01000105
Key_AudioCycleTrack0x01000106
Key_Time0x01000107
Key_Hibernate0x01000108
Key_View0x01000109
Key_TopMenu0x0100010a
Key_PowerDown0x0100010b
Key_Suspend0x0100010c
Key_ContrastAdjust0x0100010d
Key_LaunchG0x0100010e
Key_LaunchH0x0100010f
Key_MediaLast0x0100ffff
// Keypad navigation keys
Key_Select0x01010000
Key_Yes0x01010001
Key_No0x01010002
// Newer misc keys
Key_Cancel0x01020001
Key_Printer0x01020002
Key_Execute0x01020003
Key_Sleep0x01020004
Key_Play0x01020005Not the same as Key_MediaPlay
Key_Zoom0x01020006
// Key_Jisho = 0x01020007, // IME: Dictionary key
// Key_Oyayubi_Left = 0x01020008, // IME: Left Oyayubi key
// Key_Oyayubi_Right = 0x01020009, // IME: Right Oyayubi key
// Device keys
Key_Context10x01100000
Key_Context20x01100001
Key_Context30x01100002
Key_Context40x01100003
Key_Call0x01100004set absolute state to in a call (do not toggle state)
Key_Hangup0x01100005set absolute state to hang up (do not toggle state)
Key_Flip0x01100006
Key_ToggleCallHangup0x01100007a toggle key for answering, or hanging up, based on current call state
Key_VoiceDial0x01100008
Key_LastNumberRedial0x01100009
Key_Camera0x01100020
Key_CameraFocus0x01100021
Key_unknown0x01ffffff

Qt键盘的一个小知识

Qt::Key_ReturnQt::Key_Enter比较

相同之处

  • Qt::Key_ReturnQt::Key_Enter都是回车键.

不同之处

  • Qt::Key_Return是字母键盘的回车键;
  • Qt::Key_Enter则为数字键盘的回车键.

理解动态库与静态库区别

静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。

静态链接库是什么?

一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
将自己设计的类导出为二进制形式的可执行代码。静态链接库有两种形式

  • MSVC编译器生成的文件后缀为 “.lib”
  • MinGW编译器生成的文件后缀为 “.a”

动态链接库是什么?

动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。

总结

从产品化的角度,发布的算法库或功能库尽量使动态库,这样方便更新和升级,不必重新编译整个可执行文件,只需新版本动态库替换掉旧动态库即可。
从函数库集成的角度,若要将发布的所有子库(不止一个)集成为一个动态库向外提供接口,那么就需要将所有子库编译为静态库,这样所有子库就可以全部编译进目标动态库中,由最终的一个集成库向外提供功能。

在Qt中生成和调用静态库

在QtCreator中按照如下步骤创建静态库,静态库名为MyLib。我们这里选用的构建套件为Desktop Qt 5.9.0 MinGW 32bit。

选择静态链接库,用来创建静态库。

创建好项目之后,我们会得到一个.pro文件,一个.cpp源文件,一个.h头文件,源文件和头文件中包含一个名为MyLib的类,我们这里简单使用,只给这个类的构造函数中添加一个弹窗“Hello World”。

//mylib.h
#ifndef MYLIB_H
#define MYLIB_H

#include <QMessageBox>
class MyLib
{    
    public:
        MyLib();
};

#endif // MYLIB_H
//mylib.cpp
#include "mylib.h"

MyLib::MyLib()
{
    QMessageBox::information(NULL , "Title" , "Hello World");
}

将该项目进行编译,生成两个文件:libMyLib.a和mylib.o,我们真正要用到的只需要libMyLib.a以及我们的MyLib.h头文件即可。
接下来我们创建一个UseLib的Qt Widgets Application项目来使用我们刚刚编译的静态库。
创建好UseLib项目后,我们把MyLib.h和MyLib.a放到UseLib项目源码文件夹中,并在UseLib.pro中加入如下代码用以包含我们的静态链接库:

//UseLib.pro
LIBS += \
        $$PWD/libMyLib.a

接下来我们将MyLib.h添加为UseLib项目的文件,并在mainwindow.h中include上MyLib.h。
接下来就可以在mainwindow.h和mainwindow.cpp中使用该静态链接库里的MyLib对象啦。
项目结构如下:

在mainwindow.cpp的MainWindow类的构造函数中我们加入如下代码:

    MyLib *lib = new MyLib();

编译并运行,就可以看到在我们的UseLib项目的界面出来之前,就已经执行了我们静态库MyLib中的构造函数中的弹窗~调用静态库成功!

在Qt中生成和调用动态库

在QtCreator中按照如下步骤创建动态库,动态库名为MyLib。我们这里选用的构建套件为Desktop Qt 5.9.0 MinGW 32bit。

选择共享库,用来创建动态库。

创建好项目之后,我们会得到一个.pro文件,一个.cpp源文件,一个.h头文件,一个mylib_global.h头文件,源文件和头文件中包含一个名为MyLib的类,我们这里简单使用,只给这个类的构造函数中添加一个弹窗“Hello World”。

//mylib.h
#ifndef MYLIB_H
#define MYLIB_H

#include <QMessageBox>
class MyLib
{    
    public:
        MyLib();
};

#endif // MYLIB_H
//mylib.cpp
#include "mylib.h"

MyLib::MyLib()
{
    QMessageBox::information(NULL , "dll title" , "Hello World");
}

将该项目进行编译,生成三个文件:libMyLib.a、MyLib.dll和mylib.o,我们真正要用到的只需要MyLib.dll以及我们的MyLib.h头文件即可。
接下来我们创建一个UseLib的Qt Widgets Application项目来使用我们刚刚编译的静态库。
创建好UseLib项目后,我们把MyLib.h和MyLib.dll放到UseLib项目源码文件夹中,并在UseLib.pro中加入如下代码用以包含我们的动态链接库:

//UseLib.pro
LIBS += -L$$PWD -lMyLib

接下来我们将MyLib.h添加为UseLib项目的文件,并在mainwindow.h中include上MyLib.h。
接下来就可以在mainwindow.h和mainwindow.cpp中使用该动态链接库里的MyLib对象啦。
项目结构如下:

在mainwindow.cpp的MainWindow类的构造函数中我们加入如下代码:

    MyLib *lib = new MyLib();

编译并运行,就可以看到在我们的UseLib项目的界面出来之前,就已经执行了我们动态库MyLib中的构造函数中的弹窗~调用动态库成功!

需要注意的是,如果要将应用程序发布,需要将动态链接库跟随主程序一起发布。

将dylib库嵌入MacOS应用的方法

当你的应用程序使用了第三方的动态库,或自己开发的动态库的时候,使用macdeployqt则会报错:

ERROR: no file at "/usr/lib/libXXXX.1.dylib"

用otool -L untitled.app/Contents/MacOS/untitled 可以看到输出中包含如下这一行。

libXXXX.1.dylib (compatibility version 1.0.0, current version 1.0.1)

这一行表示你的应用程序找这个动态库是相对路径的,即要求你的这个动态库在/usr/lib目录下或/usr/local/lib目录下。你双击编译出的用用程序提示无法打开,点击报告会显示为找不到库。其实是在/usr/lib目录下或/usr/local/lib目录下 找不到这个库。你手工放置库文件到这个目录即可双击运行。注意:你没有权限把库放到/usr/lib下,因此你放到/usr/local/lib即可。
注意在你动态库的xxx.pro文件中加入如下的配置。否则,双击应用程序的时候会到/usr/lib找,而不是在/usr/local/lib找。

unix {
target.path = /usr/local/lib
INSTALLS += target
}

为了发布出去的应用程序不再在/usr/local/lib目录下找对应的动态库。而是在bundle包(目录)中查找。从而用户复制你的bundle到“应用程序”目录即可直接运行。因此你需要修改应用程序记录动态库的路径。修改方法如下:

install_name_tool -change "libXXXX.1.dylib" "@rpath/xxxx/libXXXX.1.dylib" untitled.app/Contents/MacOS/untitled 

命令表示,把bundle包里面的应用程序untitled储存的此库的路径从”libXXXX.1.dylib”改为”@rpath/xxxx/libXXXX.1.dylib”。

执行完此命令后,找到untitled这个编译好的程序右键“打开包内容/Show Package Contents”,然后跳转到bundle的包内部目录里面,切换到“Contents”目录下的Frameworks目录中,然后创建一个目录“xxxx”(自己起的名字)然后把你制作的动态库或第三方的动态库放到这个目录。保证库的名字和”@rpath/xxxx/libXXXX.1.dylib”写的库的名字对应上。
以上就都做好了,现在用otool工具检测一下应用程序untitled包含库的路径:

otool -L untitled.app/Contents/MacOS/untitled

就会变成以@rpath开头的相对路径了。这次你双击“untitled”程序,程序就不会报错说找不到库了。
然后执行以下命令打包为dmg安装包。

macdeployqt ./build-untitled-Desktop_Qt_5_12_3_clang_64bit-Release/untitled.app -dmg

使用到的工具介绍

  • macdeployqt:qt的提供的工具,可以把应用程序依赖的动态库,查找出来并放到一个文件夹中。
  • macdeployqt -dmg:在macdeployqt基础上,再打包生成dmg安装包
  • otool -L:查看一个动态库或应用程序的依赖
  • install_name_tool -change: 改变应用程序或库的依赖库路径。

1、打开Visual Studio,新建一个C#的Class Library项目(这里选择的是.Net Framework 4),项目名为CSharpDll。

2、由于默认没有引入Forms等UI库,先在reference中添加引用System.Windows.Forms以便可以在测试中使用MessageBox等。

3、最终C#编写的dll的源代码如下图所示,命名空间为CSharpDll,公共类为CSharpClass。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace CSharpDll
{
    public class CSharpClass
    {
        public CSharpClass() { }
        public int add(int a , int b)
        {
            return a + b;
        }

        public void substract( int a , int b , ref int c)
        {
            c = a - b;
        }

        public static void showBox(string str)
        {
            MessageBox.Show("C#:" + str);
        }
    }
}

里面包含一个加法add,一个减法substract(为了测试指针,所以在减法的返回类型是void,而把计算结果通过ref参数c给返回),一个showBox方法(里面采用C#的MessageBox对话框显示用户输入的参数字串)

4、对project进行release build,在release目录下生成了CSharpDll.dll(待会用到)。

5、关闭CSharpDll项目,另外新建一个C++ CLR类型的Class Library项目(选择与C#项目相同的.Net Framework 4),项目名称为CppDll。

一开始我用的VS2019,发现VS2019好像无法新建 C++ CLR类型的Class Library项目了,所以学习微软的技术一定要小心,学习主流的支持很久的技术,尽量不要学习新出的技术,如果必须学新技术,一定要认真考量,一些边缘化的技术一定不要学习,没准哪天微软就不维护了。

6、选择Project->CppDll Properties…,在弹出的属性页面选择“Add New Reference..”,点击“browsing.”后选择CSharpDll项目中release目录下的CSharpDll.dll。

7、选择CSharpDll.dll后,可以看到在项目属性的References中出现了CSharpDll这个Library。

8、在CppDll项目中的CppDll.h中利用_declspec(dllexport)导出对应的3个接口函数add,substract,showBox。需要using namespace System::Reflection,对于这里的showBox,其参数不能采用CSharpDll里面的showBox参数的String类型,而是使用const char* 类型。

主要代码如下所示:

// CppDll.h

#pragma once

using namespace System;
using namespace System::Reflection;

__declspec(dllexport) int add(int a, int b)
{
	CSharpDll::CSharpClass obj;
	return obj.add(a, b);
}

__declspec(dllexport) void substract(int a, int b, int *c)
{
	CSharpDll::CSharpClass obj;
	obj.substract(a, b, *c);
}

__declspec(dllexport) void showBox(const char* content)
{
	CSharpDll::CSharpClass obj;
	String^ str = gcnew String(content);
	obj.showBox(str);
}

namespace CppDll {

	public ref class Class1
	{
		// TODO:  在此处添加此类的方法。
	};
}

9、选择release方式build CppDll项目,在release文件夹中生成了CppDll.dll文件,可以看到同时其也将引用的CSharpDll.dll也给拷贝到release文件夹中了。

10、接下来在Qt中进行调用, 在QtCreator中新建一个TestCSharpDll GUI项目,编译器选的mingw。通过VS自带的命令行工具中的dumpbin工具可以查看CppDll.dll导出的函数接口。

dumpbin -exports (+dll路径)

在TestCSharpDll工程中通过typedef定义函数指针,同时采用QLibrary动态加载并resolve函数。

在这里.dll的路径设为当前目录下“./CppDllMingW.dll”,也就是编译好的程序exe同一目录下的dll,去resolve由普通导出方式的接口即“?add@@YAHHH@Z”。

主要代码如下所示:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QLibrary>
#include<QMessageBox>
typedef int (*x_add)(int a , int b);
typedef void (*x_substract)(int a , int b , int* c);
typedef void (*x_showBox)(const char* content);

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

//add
void MainWindow::on_pushButton_clicked()
{
    int a = ui->lineEdit->text().toInt();
    int b = ui->lineEdit_2->text().toInt();
    QLibrary library("./CppDll.dll");
    if(library.load()){
        x_add add = (x_add)library.resolve("?add@@YAHHH@Z");
        if(add){
            QString str = QString::number(add(a , b));
            QMessageBox::information(this , "call add from dll" , str);
        }
    }
}

//sub
void MainWindow::on_pushButton_2_clicked()
{
    int a = ui->lineEdit_3->text().toInt();
    int b = ui->lineEdit_4->text().toInt();
    int c = 0;
    QLibrary library("./CppDll.dll");
    if(library.load()){
        x_substract sub = (x_substract)library.resolve("?substract@@YAXHHPAH@Z");
        if(sub){
            sub(a , b , &c);
            QString str = QString::number(c);
            QMessageBox::information(this , "call sub from dll" , str);
        }
    }
}

//showBox
void MainWindow::on_pushButton_3_clicked()
{
    QLibrary library("./CppDll.dll");
    if(library.load()){
        x_showBox showBox = (x_showBox)library.resolve("?showBox@@YAXPBD@Z");
        if(showBox){
            showBox("showBox!");
        }
    }
}

编译TestCSharpDll工程,将CppDll.dll和CSharpDll.dll复制到同一目录下,执行TestCSharpDll.exe,可看出点击按钮后,通过QLibrary进行动态resolve,均正常调用。

调用add函数
调用sub函数
调用showBox函数

最好是将相关dll置于同一目录下运行,不然会出现“未能加载文件或程序集”的异常。针对.lib链接方式,理应是置于同一目录下。而针对QLibrary进行resolve方式,可能通常一开始的想法是,CppDll.dll和CSharpDll.dll放在与程序不同目录的地方,程序中利用了QLibrary指定了CppDll.dll的方式进行加载,而CppDll.dll和CSharpDll.dll,因此程序调用CppDll.dll里面的函数时,CppDll.dll会找到与CppDll.dll同一目录下的CSharpDll.dll,然而CppDll.dll在被程序进行加载时,其继承了程序的环境变量,因此会从程序的当前目录下去查找,所以最好还是将CppDll.dll和CSharpDll.dll放置于程序同一目录下,同时QLibrary加载当前目录下的CppDll.dll。当然,部署到另外一台机器上时,目标机器还是需要安装.Net Framework,其版本至少不能低于当前CSharpDll.dll所使用的版本。

qmake简介

qmake是Qt的构建工具,主要作用是解析pro格式的项目文件、生成编译规则(Makefiles或其它)。

qmake是一个比较古老的工具,很多功能使用perl脚本实现。

Qt官方之前开发的Qbs,后来又宣布不再更新,现在又大力支持CMake。

在这样的背景下,qmake依然是当下主要的构建工具,所以qmake的一些技巧还是有必要掌握的。

qmake本身作为一个可执行程序,也是有一些参数的,但这不是本文的重点,本文的重点都在pro文件里。

pro文件中,除了常规的组织项目结构外,还可以做很多事情, 比如 指定编译选项、链接选项、制定目标生成规则、扩展编译规则 等等。

pro文件中的qmake语法,包括 变量声明和使用、内建变量、替换函数、测试函数等,帮助文档都有详细的介绍。

添加第三方库

在C++开发,使用第三方库也是家常便饭了,这是一个必备的技能。

这里首选的方法,是使用QtCreator提供的添加库功能。在pro文件里(或者项目文件夹), 鼠标右键->添加库,然后根据自己的需要下一步、下一步点一下即可。

熟练的人也可以直接按pro语法(perl语法)写,给LIBS变量赋值。

  • 直接使用链接库的全路径
  LIBS += c:/mylibs/XXX.lib

我们都知道windows系统默认的路径分割符是\,但在qmake中要写成\才行。qmake也支持写成’/’,其它unix系统又都是’/’,所以干脆都写成’/’,方便处理。

  • 路径中包含空格等特殊字符,用引号括起来
  LIBS += "C:/mylibs/xxx libs/XXX.lib"
  • 分别指定路径和库
  LIBS += "-LC:/mylibs/xxx libs" -lxxx

这里的LIBS指定要链接的库,’-L’是指定链接库的路径,’-l’指定要链接的库名称。
名称可以省略lib前缀和 扩展名后缀,Qt会自动处理。拓展名包括 ‘.so’ ‘.dll’ ‘.dylib’ 等。

  • 分平台条件链接
  win32:LIBS += "C:/mylibs/xxx libs/xxx.lib"
  unix:LIBS += "-L/home/user/xxx libs" -lxxx

条件链接可以很方便地实现不同平台链接不同的库。
这里的 win32 unix 是在选择了不同的编译器环境时,qmake分别预置的变量。

原理

Qt内置了一些perl脚本,在执行qmake解析时会包含这些脚本。其中一些脚本会来处理这个LIBS变量,将其转换成编译器/链接器的参数。

内置的脚本路径在[QTDIR]/mkspecs/features文件夹下,扩展名为prf。

后续的很多变量,也是一样的原理, 只是处理方式各不相同。

很多pro文件的语法、功能实现,都可以参考这些prf来实现。

Qt程序员都知道的一件事:有时候修改了信号/槽相关的代码,不能正常运行,要重新qmake一下,才会生效。

本质上就是在重新触发[QTDIR]/mkspecs/features/moc.prf这个脚本。

影子构建

影子构建,就是编译生成的产物和源代码在不同的文件夹。这样可以防止源代码文件夹被污染。

QtCreator默认导入pro工程时,就会生成一个影子构建路径。比如这样:

F:\Dev\Qt\Desktop_Qt_5_12_3_MSVC2017_64bit-Debug

之后编译项目时生成的中间文件及目标文件,都在这个文件夹中。

这个路径很长,而且编译器或者编译选项不同时都有可能不一样。

有时候要做一些特定的操作:比如目标exe生成到特定目录、拷贝资源文件等等,直接用这个路径会不太方便/不太可靠,我们需要一些定制。

指定目标路径

  DESTDIR = $$PWD/bin

通过给DESTDIR变量赋值, 可以指定生成的lib/exe放在哪个目录下

‘PWD’是qmake内置变量,’$$‘是内置变量取值的写法。’/bin’是字符串拼接在变量后面。

指定中间件生成路径

可以通过这几个变量指定中间件生成的路径

config(debug, debug|release) {
    OBJECTS_DIR = build/debug/obj
    MOC_DIR = build/debug/moc
    RCC_DIR = build/debug/rcc
    UI_DIR = build/debug/uic
} else {
    OBJECTS_DIR = build/release/obj
    MOC_DIR = build/release/moc
    RCC_DIR = build/release/rcc
    UI_DIR = build/release/uic
}

config(debug, debug|release) 是一个条件表达式,可以理解为

if (debug === true) {

} else if (release == true) {

}

注意: 按照perl语法,那个左大括号’{‘不能换行,要和前面的表达式在同一行。

上面这种指定中间件路径的方式,在QtCreator中有默认路径所以没有太大意义,不过在命令行编译时这样写却很有用。

Qt官方信息

  • Qt官网:http://qt.digia.com/
  • Qt开源官网:http://qt-project.org/
  • Qt最新版本下载:http://qt-project.org/downloads
  • Qt所有版本下载:ftp://ftp.qt-project.org/qt/source/
  • Qt Creator所有版本下载:ftp://ftp.qt-project.org/qtcreator/

Qt与Qt Creator简介

Qt是一个跨平台应用程序和 UI 开发框架。使用 Qt 您只需一次性开发应用程序,无须重新编写源代码,便可跨不同桌面和嵌入式操作系统部署这些应用程序。

Qt Creator 是全新的跨平台Qt IDE,可单独使用,也可与 Qt 库和开发工具组成 一套完整的SDK. 其中包括:高级 C++ 代码编辑器,项目和生成管理工具,集成的上下文相关的帮助系统,图形化调试器,代码管理和浏览工具。

Qt功能与特性

  • 直观的 C++ 类库:模块化 Qt C++ 类库提供一套丰富的应用程序生成块 (block),包含了构建高级跨平台应用程序所需的全部功能。具有直观,易学、易用,生成好理解、易维护的代码等特点。
  • 跨桌面和嵌入式操作系统的移植性:使用 Qt,您只需一次性开发应用程序,就可跨不同桌面和嵌入式操作系统进行部署,而无须重新编写源代码,可以说Qt无处不在(QtEverywhere) 。
  • 使用单一的源代码库定位多个操作系统;
  • 通过重新利用代码可将代码跨设备进行部署;
  • 无须考虑平台,可重新分配开发资源;
  • 代码不受担忧平台更改影响的长远考虑 ;
  • 使开发人员专注于构建软件的核心价值,而不是维护 API 。
  • 具有跨平台 IDE 的集成开发工具:Qt Creator 是专为满足 Qt 开发人员需求而量身定制的跨平台集成开发环境 (IDE)。Qt Creator 可在 Windows、Linux/X11 和 Mac OS X 桌面操作系统上运行,供开发人员针对多个桌面和移动设备平台创建应用程序。
  • 在嵌入式系统上的高运行时间性能,占用资源少。

Qt Creator功能和特性

  • 复杂代码编辑器:Qt Creator 的高级代码编辑器支持编辑 C++ 和 QML (JavaScript)、上下文相关帮助、代码完成功能、本机代码转化及其他功能。
  • 版本控制:Qt Creator 汇集了最流行的版本控制系统,包括 Git、Subversion、Perforce、CVS 和 Mercurial。
  • 集成用户界面设计器:Qt Creator 提供了两个集成的可视化编辑器:用于通过 Qt widget 生成用户界面的 Qt Designer,以及用于通过 QML 语言开发动态用户界面的 Qt Quick Designer*。
  • 项目和编译管理 :无论是导入现有项目还是创建一个全新项目,Qt Creator 都能生成所有必要的文件。包括对 cross-qmake 和 Cmake 的支持。
  • 桌面和移动平台:Qt Creator 支持在桌面系统和移动设备中编译和运行 Qt 应用程序。通过编译设置您可以在目标平台之间快速切换。
  • Qt 模拟器:Qt模拟器是诺基亚 Qt SDK的一部分,可在与目标移动设备相似的环境中对移动设备的 Qt 应用程序进行测试。

Qt的历史

  • 1996年Qt 上市
  • Qt 已成为数以万计商业和开源应用程序的基础
  • Qt 的软件授权机制具有经受市场检验的双重授权(开源与商业)模式
  • Qt Software 的前身为 Trolltech(奇趣科技)。 Trolltech (奇趣科技)始创于1994年
  • Trolltech (奇趣科技)于2008年6月被 Nokia 收购,加速了其跨平台开发战略
  • 2012年8月芬兰IT业务供应商Digia全面收购诺基亚Qt业务及其技术

Qt所支持的平台

1.嵌入式 Linux (Embedded Linux)

Qt for Embedded Linux® 是用于嵌入式 Linux 所支持设备的领先应用程序架构。您可以使用 Qt 创建具有独特用户体验的具备高效内存效率的设备和应用程序。Qt 可以在任何支持 Linux 的平台上运行。Qt 的直观 API,让您只须少数几行代码便可以更短的时间实现更高端的功能。

特点:

(1) 用于Linux 的紧凑的视窗系统;

(2) 用于广泛的应用程序处理器的开发;

(3) 移植桌面代码至嵌入式平台,或通过重新编译,反之亦然;

(4) 编译移除不常使用的组件与功能;

(5) 利用系统资源并实现本地化性能;

(6) 开发嵌入式设备犹如开发桌面系统一样轻松简单。

Qt 除了提供所有 工具 以及 API 与 类库 ,( 如WebKit ) 外,Qt for Embedded Linux 还提供用于最优化嵌入式开发环境的主要组件。

  • 紧凑高效的视窗系统 (QWS):Qt 构建在标准的 API 上,应用于嵌入式 Linux 设备,并带有自己的紧凑视窗系统。基于 Qt 的应用程序直接写入Linux 帧缓冲,解除了您对 X11 视窗系统的需求。具有减少内存消耗,占位更小,可利用硬件加速图形的优势,可编译移除不常使用的组件与功能等特点。
  • 虚拟帧缓冲 (QVFb):Qt for Embedded Linux 提供一个虚拟帧缓冲器,可以采用点对点逐像素匹配物理设备显示。具有真实的测试构架,在桌面系统上嵌入式测试,模拟物理设备显示的宽度、高度与色深等特点。
  • 进程间通讯 (IPC) :IPC (进程间通讯)可以创建丰富的多应用程序用户体验。定义进程间通讯的两个主要概念即:信道与消息。可以进程并向信道发送消息,任何时候只要到一个进程便可创建信道。
  • 扩展的字体格式:Qt 支持嵌入式 Linux 上的多种字体格式,包括:TrueType®, Postscript®Type1 与 Qt 预呈现字体。Qt 扩展了Unicode 支持,包括:构建时自动数据抽取和运行时自动更新。另外Qt还提供定制字体格式的插件,允许在运行时轻松添加新字体引擎。应用程序间的字体共享功能可以提高内存效率。

基本要求:

开发环境:Linux 内核 2.4 或更高;GCC 版本 3.3 或更高;用于 MIPS® GCC 版本 3.4. 或更高。
占用存储空间:存储空间取决于配置,压缩后: 1.7 – 4.1 MB,未压缩: 3.6 – 9.0 MB 。
硬件平台:易于载入任何支持带 C++ 编译器和帧缓冲器驱动Linux 的处理器。支持 ARM®,x86®, MIPS®, PowerPC® 。

2.Mac 平台

Qt 包括一套集成的开发工具,可加快在 Mac 平台上的开发。在编写 Qt 时,并不需要去设想底层处理器的数字表示法、字节序或架构。要在 Apple平台上支持 Intel 硬件,Qt 客户只需重新编辑其应用程序即可。

3.Windows平台

使用 Qt,只需一次性构建应用程序,无须重新编写源代码,便可跨多个 Windows操作系统的版本进行部署。Qt应用程序支持 WindowsVista、Server 2003、XP、NT4、Me/98 和 Windows CE。

4.Linux/X11平台

Qt 包括一套集成的开发工具,可加快在 X11 平台上的开发。Qt 由于是 KDE 桌面环境的基础,在各个 Linux 社区人尽皆知。几乎 KDE 中的所有功能都是基于 Qt 开发的, 而且 Qt 是全球社区成员用来开发 成千上万的开源 KDE 应用程序的基础。

5.Windows CE/Mobile

Qt 是用 C++ 开发的应用程序和用户界面框架。通过直观的 API,您可以使用 Qt 为大量的设备编写功能丰富的高性能应用程序。Qt 包括一套丰富的工具集与直观的API,意味着只须少数几行代码便可以更短的时间实现更高端的功能。

主要特点:

(1)硬件依存性极小;
(2)支持多数现有的 Windows CE 配置;
(3)对于自定义的硬件配置亦轻松构建;
(4)移植桌面代码至嵌入式平台,或通过重新编译,反之亦然;
(5)编译移除不常使用的组件与功能;
(6)利用系统资源并实现高性能;
(7)开发嵌入式设备尤如开发桌面系统一样轻松简单。

Qt 除了提供所有 工具 以及 API 与 类库 外,Qtfor Windows CE 还提供用于最优化嵌入式开发环境的附加功能。

本地化和可定制的外观:Qt 在使用时,可以支持 Windows Mobile 和 Windows CE 两种样式。 在运行时,Qt 应用程序将检测使用哪一种样式。 采用 Qt 样式表单,您只需要花费用于传统 UI 风格的少许时间和代码行,便可以轻松定制您的应用程序外观。特点:基于HTML 层叠式样式表 (CSS);适用于全部 widget;任何熟悉 CSS 技术的人员都可以定义复杂的样式。
先进的文本布局引擎:Qt for Windows CE 支持 TrueType® 和点阵字体。同时 Qt 还支持扩展的 Unicode 和从右至左的书写语言。Qt 的富文本引擎增加了新的功能用于复杂的文本布局,包括制表和路径追踪,以及环绕图形的文本。
基本要求:

开发环境: Microsoft® Visual Studio® 2005 (Standard Edition) 或更高ActivePerl 。
占用存储空间:紧凑配置 – 4.8 MB,全配置 – 8.4 MB。
操作系统:Windows CE 5 或更高,Windows Mobile 5 或更高。
硬件平台:支持 ARM®, x86®,(在 SH4® 和 MIPS® 上编译) 。

6.塞班平台(Symbian)

Qt 通过和S60 框架的集成为 Symbian平台提供了支持。在最新版的QtSDK 1.1中我们可以直接生成可以在塞班设备上运行的sis文件。

7.MeeGo平台 (Maemo 6 现更名为 MeeGo)

Qt 是一个功能全面的应用程序和用户界面框架,用来开发Maemo 应用程序,也可跨各主要设备和桌面操作系统部署这些程序且无需重新编写源代码的。 如果您在多数情况下开发适用于Symbian、Maemo 或 MeeGo 平台的应用程序,可以使用免费 LGPL 授权方式的 Qt。

Qt将为诺基亚设备运行MeeGo (Harmattan) 提供依托,并可为所有即将推出的 MeeGo 设备中的应用程序开发提供 API,为 Qt 开发人员提供了更多平台。不久,MeeGo 设备就会完全支持 (X11) Qt 。

Qt类库

模块化 Qt C++ 类库提供一套丰富的应用程序生成块(block),包含了生成高级跨平台应用程序所需的全部功能。

(1)先进的图形用户界面(GUI):Qt为您在桌面与嵌入式平台上开发先进的GUI应用程序,带来所有需要的功能。Qt使用所支持平台的本地化图形API,充分利用系统资源并给予应用程序本地化的界面。

  • 从按钮和对话框到树形视图与表格都具有完整的控件(窗体)
  • 自动缩放,字体、语言与屏幕定位识别布局引擎
  • 支持抗锯齿、矢量变形以及可缩放矢量图形 (SVG)
  • 具有样式API和窗体样式表,可完全自定义用户界面
  • 支持嵌入式设备的硬件加速图形和多重显示功能

(2)基于OpenGL ®与OpenGL ®Es的3D图形:OpenGL® 是一个标准的图形库,用于构建跨平台和支持硬件加速的高性能可视化应用程序。虽然OpenGL完美支持3D图形,但却不支持创建应用程序用户界面。Qt通过与OpenGL 的紧密集成解决了这一难题。

  • 在您的应用程序中轻松加入3D图形
  • 在嵌入式Linux 与Windows CE 平台上使用OpenGL ES和OpenGL绘画引擎
  • 利用系统资源实现最佳图形性能
  • 支持Windows 平台上的Direct3D®

(3)多线程:多线程编程是一个执行资源密集型操作而不会冻结应用程序用户界面的有效典范。Qt的跨平台多线程功能简化了并行编程,另外它附加的同步功能可以更加轻松地利用多核架构。

  • 管理线程、数据和对象更加轻松
  • 基于Qt的信号与槽,实现跨线程类型安全的对象间通讯
  • 高端API可以编译多线程程序而无须使用底端基元

(4)嵌入式设备的紧凑视窗系统:Qt构建在标准的 API基础上,用于具有轻量级window系统的嵌入式 Linux 设备。基于 Qt的应用程序直接写入 Linux 帧缓冲,解除了您对 X11 视窗系统的需求。

  • 减少内存消耗,内存占用更小
  • 可以编译移除不常使用的组件与功能
  • 可以利用硬件加速图形
  • 在桌面系统上的虚拟帧缓冲可用于嵌入式开发与调试

(5)对象间通讯:在开发用户图形界面中,一个常见的、重复发生系统崩溃与问题的症结根源是如何在不同组件之间进行通信。对于该问题,Qt 的解决方案是信号与槽机制,即执行Observer设计模式。我们可以简单理解为当特殊事件发生的时候,信号就被发出了,一个插槽就是一个函数,被称作特定信号的响应。

  • 信号与槽机制是类型安全的(type safe)
  • 任意信号都可以连接任意或多个插槽,或跨多个线程
  • 简化真正的组件编程

(6)2D图形:Qt给您提供一个功能强大的2D图形画布,用以管理和集成大量的图形元素。

  • 高精度可视化大量元素
  • 将窗体互动嵌入至图形场景中
  • 支持缩放、旋转、动画与变换

(7)多媒体框架:Qt使用

Phonon多媒体框架为众多的多媒体格式提供跨桌面与嵌入式操作系统的回放功能。Phonon可以轻松将音频与视频回放功能加入到Qt应用程序当中,并且在每个目标平台上提取多媒体格式与框架。

以平立的方式提供多媒体内容
从本地文件读取媒体或读取网络上的流媒体
提取Mac上的 QuickTime® ,Windows 上的DirectShow® 以及 Linux 上的Gstreamer

(8)WebKit集成:Qt WebKit集成,即Qt集成了WebKit功能,WebKit是KDE项目下基于 KHTML的开放源web浏览器引擎。目前 Apple®,Google™ 与Nokia等公司使用Qt WebKit集成。

将web与本地内容和服务整合在单一的富应用程序当中
快速创建整合实时web内容与服务的应用程序
使用集成在本地代码中的 HTML 与Java Script
完全控制跨平台的浏览器环境

(9)网络连接:Qt 让您网络编程更简单,并支持跨平台网络编程。

完整的客户/服务器插口提取
支持 HTTP,FTP,DNS 与异步 HTTP 1.1
无论HTML 和XML或图象与媒体文件,它都可以存取所有类型的数据

(10)XML:Qt 为XML 文件以及SAX 和 DOM 协议的C++实现,提供了一个流媒体文件读写器。同时 Qt 还包含了 XQuery – 一个简单的类似 SQL的查询语言,用于解析XML文件来选择和聚合所需要的XML元素,并且将它们转换成XML输出或其它格式的输出。

  • 仅需少数几行代码便可实现先进的 XML 查询
  • 完全支持 XQuery 1.0 和 XPath 2.0
  • 在您自己的应用程序中从XML查询、抽取和转换数据

(11)脚本引擎:Qt 包含一个完全集成 ECMA 标准的脚本引擎。 QtScript 提供 QObject 集成,把 Qt的信号与槽机制整合成脚本,并且实现了C++ 与脚本的集成。

基于ECMA 标准的脚本语言(ECMAScript 3是JavaScript1.5的基础)

  • 为简化的对象间通讯使用Qt的信号与槽机制
  • 开创新的契机将脚本与您的Qt应用程序相集成

(12)数据库:Qt 帮助您将数据库与您的Qt应用程序无缝集成。Qt支持所有主要的数据驱动,并可让您将SQL发送到数据库服务器,或者让 Qt SQL类自动生成 SQL 查询。

  • 支持所有主要的数据库驱动
  • 以多种视图或数据识别表单方式显示数据

Qt Quick介绍

Qt Quick是在Qt4.7中被引进的一项技术。Qt Quick 是一种高级用户界面技术,开发人员和设计人员可用它协同创建动画触摸式用户界面和应用程序。它由三部分构成:
(1)QML:像 JavaScript 一样的声明式语言;
(2)Qt Creator:在 Qt IDE中的直观工具;
(3)Qt Declarative:强大的 C++ 模块。

1.主要组成:

  • QML:基于 JavaScript 的直观语言 :QML 是一种简便易用的语言,开发人员与用户界面设计人员无需任何 C++ 知识,即可用其描绘出用户界面的外观和功能。
  • 面向开发人员和设计人员的共享工具:Qt Creator IDE2.1 版将集成一套开发人员与用户界面设计人员可共享,用以创建和实施 Qt Quick 项目的通用工具。
  • 通过 C++ 推动 QML 应用程序:在 Qt 库中的全新Declarative 模块支持生成动态可定制的用户界面,以及通过 C++ 拓展 QML 应用程序。

2.功能特点:

  • 快速开发动画式流畅多变的用户界面:通过直观的 QML 语言和一套丰富的 QMLElements——UI 和行为生成块——您可以快速创建出令人印象深刻的用户界面,比您想象的还要快。
  • 无需 C++ 知识:如果您具有 JavaScript 的经验或掌握基本的网络技术 (如 HTML 和 CSS),您就可以通过 QML 取得非常不错的成果。
  • 瞄准数以百万计的触摸屏设备:使用 Qt Quick,您可以为数以百万计的 Symbian 和 MeeGo 设备生成应用程序,或为各种类型的触摸屏消费类电子设备创建用户界面。

3.应用领域:

  • 汽车信息娱乐系统 UI:Cybercom Group 的用户界面设计人员与开发人员尝试使用 Qt Quick 为其汽车信息娱乐平台设计 UI——并取得了令人满意的结果。
  • 社交媒体电视:mixd.tv 使用 Qt Quick 为其跨平台网络电视应用程序创建 UI,其用户可以通过社交媒体频道访问和共享在线视频的内容。
  • 联网汽车:Qt 的认证合作伙伴 Digia 很快学会了 Qt Quick 并用其创建出了包括导航、电话、游戏和音乐功能的高级汽车 UI。

Qt授权

Qt Commercial Developer License

The Qt Commercial Developer License is the correctlicense to use for the development of proprietary and/or commercial softwarewith Qt where you do not want to share any source code.

You must purchase a Qt Commercial DeveloperLicense from us or from one of our authorized resellers before you startdeveloping commercial software as you are not permitted to begin yourdevelopment with an open source licensed Qt version and convert to thecommercially license version at a later . The Qt Commercial Developer Licenseincludes a restriction that prevents the combining of code developed with theQt GNU LGPL v. 2.1 or GNU GPL v. 3.0 license versi with commerciallylicensed Qt code.

Qt GNU LGPL v. 2.1 Version

This version is available for development ofproprietary and commercial applicati in accordance with the terms andconditi of the GNU Lesser General Public License version 2.1.

Support services are available separately forpurchase.

Qt GNU GPL v. 3.0 Version

This version is freely available for the developmentof open source software governed by the GNU General Public Licenseversion 3.0 (“GPL”).

Support services are available separately forpurchase.

License Comparison Chart

CommercialLGPLGPL
License costLicense fee chargedNo license feeNo license fee
Must provide source code changes to QtNo, modificati can be closedSource code must be providedSource code must be provided
Can create proprietary applicatiYes – No source code must be disclosedYes, in accordance with the LGPL v. 2.1 termsNo, applicati are subject to the GPL and source code must be made available
Updates providedYes, immediate notice sent to those with a valid support and update agreementYes, made availableYes, made available
SupportYes, to those with a valid support and update agreementNot included but available separately for purchaseNot included but available separately for purchase
Charge for RuntimesYes, for some embedded usesNoNo

Qt5简介

Qt 5是进行Qt C++软件开发基本框架的最新版本,其中Qt Quick技术处于核心位置 。同时Qt 5能继续提供给开发人员使用原生QtC++实现精妙的用户体验和让应用程序使用OpenGl/OpenGL ES图形加速的全部功能。通过Qt 5.0提供的用户接口,开发人员能够更快的完成开发任务,针对触摸屏和平板电脑的UI转变与移植需求,也变得更加容易实现.

2012年12月19日,Digia宣布正式发行Qt 5.0。Qt 5.0是一个全新的流行于跨平台应用程序和用户界面开发框架的版本,可应用于桌面、嵌入式和移动应用程序。Qt 5 在性能、功能和易用性方面做了极大的提升,并将于明年可完全支持 Android 和 iOS 平台。Digia明确表明要使Qt 成为世界领先的跨平台开发框架。Qt 5在这个过程中具有重要的意义,它为应用程序开发人员和产品用户提供了最好的用户体验。Qt 5极大地简化了开发过程,让他们能够更快地为多个目标系统开发具有直观用户界面的程序。它还可以很平滑的过度到新的开发模式来满足触摸屏和 Tablet 的需求。

Qt 5的主要优势包括:
图形质量;中低端硬件上的高性能;跨平台移植性;支持 C + + 11; QtWebKit 2 支持的 HTML5;大幅改进QML引擎并加入新的 API;易用性并与 Qt 4 版本兼容。

  • 出色的图像处理与表现能力:Qt Quick 2 提供了基于GL的工作模式,它包括一个粒子系统和一系列着色效果集合。Qt Quick 2 让复杂图形的细腻动画和变形处理变得更加容易,也确保了低端架构中2D和3D效果的平滑渲染效果和在高端架构中一样的出色。
  • 更高效和灵活的研发: JavaScript和QML在保证对C++基础和Qt Widget支持上发挥着重要作用。Qt Webkit 2中一部分功能就在使用或者正考虑通过HTML 5,彻底的改变Qt
  • 跨平台的移植变得更加简单:对于OS开发人员来说,由于基础模块和插件模块采用了新的架构,以及Qt跨平台性的继续强化,Qt已经能够运行在所有的环境中了。而我们的下一步计划:全面的支持iOS和Android系统,现在正在如火如荼的开发中。

Qt 通过使用 OpenGL ES,大大的增加了交付令人印象深刻的图形的能力 (OpenGL ES 是一个专门为嵌入式系统和移动设备而制定的图形应用程序编程接口)。这使它更容易开发和部署具有绚丽动画效果的 2D、3D 图形应用,这些应用在各种性能级别的嵌入式设备上得到平滑运行。例如手机、平板电脑和低成本的开发平台包括 Raspberry Pi。Qt5 新的模块化的代码库使得 Qt5 的跨平台移植性变得更简单。它包含了要点模块组和附加模块组,这种设计会减小系统代码库的尺寸。整合的 Qt 平台抽象层还强调跨平台移植性,开发人员可以通过启用开发简便性为多个目标部署。Qt 支持所有主要的桌面操作系统,包括 Windows,Mac OS X 和 Linux。嵌入式操作系统包括嵌入式 Linux、Windows 嵌入式以及最广泛部署实时操作系统的嵌入式设备——VxWorks、Neutrino 和 INTEGRITY和流行的移动操作系统等等。Qt WebKit 2 集成浏览器引擎,允许轻松集成 web 内容和应用程序。它将使 HTML5 开发人员感觉轻松自如,基于 Qt WebKit 2,能够开发出兼顾响应能力和本地代码强大功能的混合应用。这些应用可以提供大量的动态内容。

只需要一个简单的重新编译,就可以直接迁移之前为 Qt4 开发的应用程序。

Qt6的技术概览

  • Qt对用户的价值体现在哪里?

Qt已经成功应用与许多不同的行业,并且在不断的横向发展,Qt对用户的核心价值体现如下:
(1)跨平台特性,用户可使用一种技术,把一套代码部署到各种的桌面、移动和嵌入式平台
(2)可扩展性,覆盖了从低端的单用途设备到高端复杂的桌面应用程序和互联系统
(3)世界一流的API、工具和文档,简化了应用程序和设备的开发流程
(4)可维护性、稳定性和兼容性,轻松维护大型代码库
(5)拥有超过100万用户的大型开发者生态
新版本的Qt需要我们进行一些调整以适应新的市场需求,同时也要把上述5个价值观作为我们工作的核心内容。 桌面应用是Qt的基础,也是Qt得以成长和强大的市场,桌面应用是我们大多数用户第一次接触Qt的地方,也是组成Qt工具链的基础。保持桌面应用的健康和成长是在其他市场也保持增长的先决条件。 嵌入式和互联设备是我们增长最快的领域。触屏设备的数量正在以指数级增长,但这些设备的硬件价格却承受着巨大压力。低端芯片组,单片机,结合中小型触摸屏的设备将无处不在。这些设备中的大多数都是功能相对简单的,但它们都需要精致流畅的用户界面。因此我们需要确保我们的产品能够瞄准这个空间,从而实现我们的可扩展的承诺。 与此同时,高端设备的用户界面的复杂性将继续增加,它们往往包括了数千个不同的屏幕和许多的应用程序。将2D和3D元素合并到一个用户界面也是很常见的,增强和虚拟现实的使用也是如此。 人工智能的元素将更广泛地应用于应用程序和设备中,我们需要有简单的方法来集成这些元素。 正在创建的互联设备数量的强劲增长,以及对用户体验的更高要求,使得我们更有必要专注于开发全球领先的工具,以简化应用程序和设备的创建流程。将UX设计人员集成到开发工作流中是我们的目标之一,但是我们还需要在许多其他领域去尝试进一步简化用户的工作。 Qt 6将是Qt的一个新的重大版本,这个版本的主要目标是为2020年以后的需求做好准备,在此次过程中我们将对代码库进行整理,使其更容易维护。重点将放在Qt中那些需要调整软件架构的部分,但是如果不破坏与Qt 5.x兼容性,那这部分就无法完成。 为了适应未来几年的需求,下面是我们会对Qt进行的的一些关键性修改。

  • 新一代的QML

QML和Qt Quick是过去几年推动Qt增长的主要技术。使用这些技术可以直观的创建用户界面是我们产品的一个独特卖点。 QML是为Qt 5创建的,但是它有一些问题和限制。这也意味着它可以大幅改进,我们计划在Qt 6中实现这些改进。我们计划如下: 引入强类型。弱类型使得用户很难对他们的代码库进行大的更改。一个强大的类型系统允许IDE和其他工具帮助用户完成这项任务,并极大地简化了维护成本。此外,它还有助于我们生成性能更好的代码和减少相关开销。 JavaScript成为QML的一个可选特性。使用QML时使用完整的JavaScript引擎会提升复杂性,而且会引起性能上的开销,尤其是在单片机等低端硬件上,性能开销更加明显。然而,这个特性在许多其他应用场景中非常有用。 去掉了QML的版本控制。通过简化QML中的某些查找规则并更改上下文属性的工作方式,我们可以消除QML中的版本控制。反过来,这将大大的简化QML引擎,极大地简化我们维护Qt Quick的工作负担,并为用户简化QML和Qt Quick的使用流程。 删除QObject和QML之间重复的数据结构 目前我们的元对象系统和QML之间有相当多重复的数据结构,这些重复的数据结构会降低启动性能,增加内存使用量。通过统一这些数据结构,我们能够减少许多开销。 避免运行时生成数据结构。这与上面的一点有关,其中许多重复的数据结构目前都是在运行时生成的。其中大多数完全有可能在编译时生成。 支持把QML编译成高效原生的C++代码。通过强大的类型和更简单的查找规则,我们可以将QML转换为高效原生的C++代码,从而显著提高运行时性能 支持隐藏实现细节。为了能够在QML组件中隐藏数据和功能,对方法和属性进行“私有化”一直是一个长期的需求。 更好的工具集成。我们当前的QML代码模型时常不完整,这使得重构和在编译时检测错误变得困难甚至不可能。通过上述更改,应该能够提供与C++相媲美的编译时诊断以及大幅改进的重构支持。

  • 下一代图形

自从Qt 5.0以来,图形领域发生了很多变化,这导致我们不得不对图形栈进行重大更改,以保持其竞争力。 在Qt 5中,我们统一使用OpenGL作为3D图形的API。从那时起,产生了许多新的API。在Linux上Vulkan是OpenGL的指定接班人,苹果正在推动Metal的发展,而微软有Direct 3D。这意味着Qt将来必须与所有这些API无缝地衔接。为了实现这一点,必须定义一个新的图形抽象层的API(类似于平台集成层的QPA),称为渲染硬件接口(RHI)。我们需要将所有的渲染基础设施(QPainter、Qt Quick Scenegraph和我们的3D支持)建立在该层的基础之上。 不同图形API的合成也导致我们必须支持不同的着色语言。Qt着色器工具模块将帮助我们在编译和运行时交叉编译着色器。 3D正在扮演越来越重要的角色,而我们目前的产品还没有一个统一的解决方案来创建同时包含2D和3D元素的UI。目前,将QML与Qt 3D或3D Studio中的内容集成是很麻烦的,并且会导致一些性能开销。此外,在2D和3D内容之间进行逐帧的动画同步和转换还没有办法做到。 3D内容与Qt Quick新的集成方式就是为了解决这个问题。在这种情况下,一个全新的渲染器将允许同时渲染2D和3D内容,并支持两者之间的任意嵌套。这将把QML转换为3D UI的UI定义语言,并且不再需要UIP格式。我们将提供一个新的技术预览版本的Qt Quick与3D支持的版本,它已经包含在了Qt 5.14中,更多的信息将会在一个单独的博文中进行说明。 最后,新的图形栈需要强大的图形素材处理的支持,它能在编译时根据目标硬件预处理这些素材并在需要时使用。比如将PNG文件转换为压缩纹理格式,将许多文件编译为纹理图集,将着色器和网格转换为优化的二进制格式等等。 我们还打算为Qt 6引入统一的主题样式引擎,这将允许我们在桌面和移动平台上获得Qt Widgets和Qt Quick的原生外观。

  • 统一并且一致的工具库

我们创建用户界面的图形工具已经被一分为二,包括Qt 3D Studio和Qt Design Studio。此外,Qt 3D Studio与Qt的其余部分有些脱节,导致了相当多的重复工作。 我们通过将Qt 3D Studio所需的功能合并回Design Studio来统一这些功能。Design Studio与Qt Creator共享了大量代码和应用/插件框架,提供了很好的设计体验,并为我们提供了在设计师和开发者之间搭建桥梁的工具。 设计工具还需要与Photoshop、Sketch、Illustrator、Maya、3D Max等内容创建工具进行良好的集成。 开发者工具需要大量的投入,这样我们才能提供对C++、QML和Python等提供最佳的支持。提供统一工具还意味着开发人员可以很容易地使用Qt Creator中的设计功能,而UX设计者可以从开发者工具的特性(如编译项目或在设备上测试)中获益。 QMake作为Qt 5中使用的构建系统有很多缺陷和限制。对于Qt 6,我们的目标是使用CMake作为标准的第三方构建系统来构建Qt。到目前为止,CMake是C++世界中使用最广泛的构建系统,我们迫切需要更好地与它集成。在QMake上我们将继续支持用户,但不会对其进一步开发或用来构建Qt框架本身。

  • 增强已有的C++ API

C++在过去的几年中发生了很大的变化。虽然Qt 5.0必须以C++ 98为基础,但现在Qt 6可以依赖C++ 17。这意味着C++提供了更多的开箱即用的功能,这在我们使用Qt 5时是没有的。我们使用Qt 6的目标是更好地集成这些能力,同时也保持向前的兼容性。 Qt 6中,我们希望把QML和Qt Quick的一些功能引入到C++中。我们致力于为QObject及其相关类引入一个新的属性系统,将QML中的绑定引擎集成到Qt的核心中,并使其在C++中可用。新的属性系统和绑定引擎将显著降低绑定的运行时开销和内存消耗,并使它们可用于Qt的所有部分,而不仅仅是Qt Quick。

  • 语言支持

在Qt 5.12中,我们引入了对Python的支持,并通过Qt为WebAssembly添加了浏览器作为新的平台。在发布6.0之后,保持并进一步扩展跨平台特性将是Qt 6系列的一个重要部分。

  • 兼容Qt 5和增量改进

与旧版本的兼容性是非常重要的,也是我们开发Qt 6时的主要需求。用户已经使用我们的框架编写了数十亿行代码,因此,我们所做的任何不兼容的更改都会给用户带来额外的成本。此外,对Qt 6的更改要求用户做的工作越多,用户升级的速度就会越慢,这将导致维护Qt 5的最后一个版本的成本更高。 因此,在用户代码中我们应该避免触发编译时或运行时错误进而使得Qt运行崩溃。如果我们必须破坏兼容性,编译时错误比运行时的静默破坏更可取(因为后者更难检测)。 当我们确实需要删除Qt的某些废弃部分,那么我们必须要确保它不会影响用户需要的部分,大部分用户都在使用的关键功能,比如Qt Widgets和其他部分毫无疑问必须保留。 我们正在计划对核心类和功能进行许多在Qt 5中无法实施的增量改进。我们的目标是保持完整的源代码兼容性,但是由于我们可以打破Qt 6的二进制兼容性,我们可以做很多在Qt 5中无法完成的清理和改进。 除了这些,我们还将对Qt 6进行其它的清理。我们将删除Qt 5中已经废弃的大部分功能(函数、类或模块)。从长远来看,这种清理将有助于节省开发人员的时间,并允许我们把更多的精力放在维护和编码上。 然而,对弃用部分的移植需要尽可能的简单,我们的用户可以完美地使用Qt 5.15 LTS增量地完成这一工作。我们的目标应该是Qt 6与Qt 5.15 LTS足够兼容,这样就可以轻松地维护一个可以同时针对两个版本编译的大型代码库。

  • 市场和技术产品结构

除了改进Qt框架和工具,我们的目标是为组件和开发工具创建一个新的市场。这个方向将面向开发、设计应用程序和嵌入式设备的直接使用者,而不是面向最终用户。因此,它将成为Qt生态系统的一个凝聚中心。它将为第三方厂商提供一个发布Qt扩展组件的场所,扩展可以是免费或商业的。 Qt在过去的几年里增长良多,当前最重要的任务就是发布一个新版本。在Qt 6中,我们有机会重组我们的产品,并将必要的框架和工具打包为一个更小的核心。我们将利用应用市场来交付我们的附加框架和工具,而不是作为与核心Qt产品紧密耦合的捆绑包。这将使我们在何时交付以及如何交付方面具有额外的灵活性,并允许我们为某些附加组件解耦发布计划。