在Qt开发中,构建系统如同项目的"骨架",它决定了:
- 代码如何编译成可执行文件
- 资源文件(如图片/UI)如何被嵌入
- 跨平台时的编译一致性
本文将深入解析Qt构建工具(qmake/CMake)的工作机制,并揭示从.pro文件到最终可执行文件的完整诞生过程...
1.常见构建系统概述
在前面一篇文章中我们使用的是qmake
构建项目,那么接下来就简单讲讲Qt
的常见构建方式。
qmake
qmake
是Qt
官方早期推荐的构建工具,语法简单。核心定位是专门为Qt
项目设计,适合中小型Qt
项目快速搭建。qmake
通过.pro
文件描述项目结构,自动生成Makefile
文件,用于后续make
或nmake
编译。而make
,是一种任务执行工具,它根据Makefile
中写好的指令,去执行各种任务(主要是编译和链接)。Makefile
就是一堆规则说明,而make
就是读这些规则去干活。
简单的例子,当新建一个Makefile文件,其中内容可以是这样的:
main:main.cpp g++ -o main mian.cpp
在命令行直接执行make
就可以直接执行Makefile,进而执行Makefile
中的命令来编译出可执行文件。
CMake
CMake
是跨平台通用构建工具,近年来Qt
官方逐步推荐CMake
,特别是Qt6
以后。CMake
支持复杂的工程组织、模块依赖管理,以及现代IDE
(如Visual Studio)更好集成,适合大型和跨平台项目。
其他构建工具
GN
GN
(Generate Ninja),也可以说Google高性能构建系统,由Google开发,主要配合Ninja
构建系统使用,特点是极致的构建速度,通常用于大型项目(例如 Chromium 浏览器),但生态局限。属于高级用法。
QBS
Qt
公司开发的构建工具,目的是取代qmake
,但推广受限,使用率较低。
2.qmake
项目的基本文件
在本小节,通过一个简单的Qt
工程项目简单介绍qmake
项目文件说明和其他细节。
2.1 新建Qt
工程
打开Qt Creater,在【欢迎】模式下,点击【创建项目...】
在Qt中,Widget
类是所有窗口类的基类,我们要创建一个窗口应用,选择【Application(Qt)】-->【Qt Widget Application】-->【选择...】

紧接着给项目命名与设置项目保存位置,接下来就是选择构建方式Define Build System,这里选择qmake-->【下一步】

接下来就是选择窗口类,其中有三个选项,分别是QMainWindow
、QWidget
以及QDialog
。其中QDialog
与QMainWidow
都继承自QWidget
,QWidget
为另外两个窗口类的基类。这里我们默认QMainWindow
。选择完毕,可以给类名重命名,这里按默认名称继续。
3个窗口类的区别
QMainWindow
:包含菜单栏、工具栏、状态栏
QWidget
:一个普通窗口,没有菜单栏、状态栏
QDialog
:对话框
在Translation File选择框中,这里会有语言选项,这里提供其他语言开发选项;这里选择【无】/【none】

在构建套件选择中,这里选择MinGW64即可,接下来就是版本控制选项,版本控制主要有两种svn和git这里不做配置,在该选择框最下方会看到我们项目创建的文件。默认选择【完成】

点击运行,弹出默认窗口,项目创建完毕。接下来为各个文件的说明。
2.2 .pro
文件
.pro
是qmake
识别的工程配置文件;指定了源码文件、头文件、需要链接的模块(如core
、gui
等)、编译参数等信息。
打开.pro
文件,会看到这样的信息

接下来,自上而下详细解释该文件的主要内容:
1.QT += core gui
这一行声明了需要用到的Qt
模块:
core
模块:提供了Qt的基本功能,比如信号与槽、字符串、容器、时间管理等。gui
模块:提供了图形界面功能,比如窗口、图形绘制、事件处理等
后期如进行数据库开发,就需要添加sql
模块;进行网络通信如TCP
,UDP
,HTTP
通信,就需要添加network
模块,等等。
2.greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
这一行做了一个条件判断:
如果Qt
版本大于4,就需要额外加上widgets
模块。因为从Qt5
开始,Widgets
(传统控件界面)被单独拆分到了widgets
模块里。也就是说,在Qt5
/Qt6
下开发传统桌面应用,需要加上这句,否则界面控件(如按钮、窗口)无法使用。
3.CONFIG += c++17
设置项目使用的C++
标准为C++17
Qt6
要求至少是C++17
标准,因此加上这一行是比较常见的。
如果要使用C++20
,可以改成
CONFIG += c++20
4.注释部分
ini
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
这段时提示你,如果希望禁止使用已启用的API(比如Qt5里已经废弃的功能),可以取消注释这一行。
这样,编译器在遇到废弃的API时,会直接报错,帮助你编写更现代、更规范的代码。
5.SOURCES += \main.cpp \mainwindow.cpp
这里列出所有的源文件 (.cpp
),qmake
根据这里的列表来编译生成目标文件。
main.cpp
:程序入口。mainwindow.cpp
:主窗口类的实现。
\
是换行符号,表示后面还有内容,保持文件可读性。
6.HEADERS += \mainwindow.h
列出头文件 (.h
文件),主要是为了方便 IDE 自动管理依赖关系,并且在一些生成 moc
文件(信号槽)时需要头文件参与。
7.FORMS += \mainwindow.ui
列出界面描述文件 (.ui
文件)。
- 这些
.ui
文件在编译时由 uic 工具自动转换成对应的C++
代码,参与最终编译。
8.部署规则(qnx
/unix
)
这一段是针对不同平台做的安装部署路径设置。
解释一下
qnx: target.path = /tmp/$${TARGET}/bin
如果编译的是QNX
系统(一种嵌入式实时操作系统),则将可执行文件安装到/tem/项目名/bin
。
else: unix:!android: target.path = /opt/$${TARGET}/bin
否则,如果是普通的Unix/Linux系统 (但不是Android),就安装到/opt/项目名/bin
。
!isEmpty(target.path): INSTALLS += target
如果设置了target.path
,就将其加入到INSTALLS变量,这样make install
时可以自动复制到目标路径。
2.3 main.cpp
这个文件是程序的入口点,在这个文件中你会看到如下内容
cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
逐行详细解释
1.#include "mainwindow.h"
这一行的作用是:
- 引入我们自己定义的主窗口类
MainWindow
的声明文件。 mainwindow.h
中通常定义了主窗口的构造、析构函数,以及窗口的基本逻辑。
为什么要引入? 因为后面 MainWindow w;
要创建对象,必须知道 MainWindow
是什么。
2.#include<QApplication>
这一行是:
- 引入 Qt 的 应用程序管理类
QApplication
。 QApplication
是 GUI 应用程序的基础,必须创建一个它的实例,才能启动窗口系统、消息循环等。
注意:
- 对于图形界面应用程序 (有窗口的),必须用
QApplication
。 - 如果是纯控制台应用程序 ,可以用
QCoreApplication
,少了图形相关功能。
3.int main(int argc, char *argv[])
这是标准的 C++
程序入口:
argc
:argument count,命令行参数的个数。argv
:argument vector,存储命令行参数的字符串数组。
Qt
为什么保留这两个参数? 因为 Qt
也支持在启动时接受命令行参数,比如指定配置文件路径、启用调试模式等,所以需要把参数传递给 QApplication
。
4.QApplication a(argc, argv);
创建一个 QApplication
对象 ,叫做 a
。
- 它负责整个程序的生命周期管理,包括:窗口调度、事件处理、系统交互等。
- 必须在创建任何窗口控件之前创建它。
注意:
- 这个
a
必须是main()
里的局部变量,而且只能有一个。 argc
和argv
被传进去,用来让 Qt 解析程序启动参数。
5.MainWindow w;
创建一个 MainWindow
类型的对象 w
。
MainWindow
是我们自己定义的主窗口类 ,通常是继承自QMainWindow
或QWidget
。- 在
MainWindow
类中,可以添加按钮、标签、菜单栏等控件,定义主界面内容。
为什么直接写在栈上(不是 new
出来)? 因为 Qt 对象有自己的父子对象机制,生命周期管理很好,局部变量在 main()
退出时自动销毁即可,不用担心内存泄漏。
6.w.show();
调用主窗口对象的 show()
方法,让窗口显示在屏幕上。
- 如果不调用
show()
,窗口是不会显示的。 show()
方法会向操作系统注册窗口、绘制界面。
注意: 如果你想在窗口显示前做一些初始化(比如设置大小、标题),可以在 show()
之前调用相关方法。
7.return a.exec();
a.exec()
进入 Qt 的事件循环(Event Loop)。- 程序开始监听用户操作(比如点击、键盘输入)、系统事件(比如窗口关闭)等。
这个函数会一直阻塞 在这里,直到所有窗口关闭,才会退出事件循环,继续执行 return
,程序结束。
简单理解 就是:a.exec()
启动了整个应用的心跳,支撑起所有交互。
2.4 界面文件和源文件(mainwindow.h
/ mainwindow.cpp
)
mainwindow.h :声明主窗口类(通常继承自 QMainWindow
或 QWidget
)。
mainwindow.cpp:定义主窗口的具体行为,如按钮点击响应、窗口布局初始化等。
2.5界面描述文件(mainwindow.ui
)
.ui
文件是用 Qt Designer 设计的界面布局文件。- 本质上是一个 XML 格式,描述了控件的摆放、信号槽连接等。
- 在编译时,uic(User Interface Compiler) 会把
.ui
文件转化为对应的 C++ 代码。
你可以通过双击 .ui
文件,在可视化界面中拖拽控件,生成界面布局。
3.项目构建流程
在完成 .pro
、源代码(.cpp
、.h
)、界面文件(.ui
)之后,Qt项目需要经过一系列构建流程,才能生成最终可以运行的可执行程序。 下面,我们从宏观角度和细节角度,一步步梳理整个流程。
3.1整体流程概述
Qt 项目的构建流程可以总结为:
rust
.pro文件 -> 生成Makefile -> 代码编译 + ui编译 + moc编译 + rcc编译 -> 链接 -> 可执行文件
具体分为如下步骤:
阶段 | 主要内容 | 备注 |
---|---|---|
1 | .pro 文件生成 Makefile |
使用 qmake |
2 | 预处理 .ui 文件 |
使用 uic (User Interface Compiler) |
3 | 处理带 Q_OBJECT 宏的类 |
使用 moc (Meta-Object Compiler) |
4 | 处理 .qrc 资源文件 |
使用 rcc (Resource Compiler) |
5 | 编译 .cpp 文件 |
调用 C++ 编译器(如 MSVC、g++ 等) |
6 | 链接生成可执行文件 | 合并所有目标文件为一个程序 |
3.2详细步骤解析
在Qt Creater中打开我们的Qt项目,点击左侧【项目】,在这里其实就能够看到项目的构建步骤,其中说明就两步:
1.qmake
:执行qmake.exe
读取.pro
文件生成Makefile
。
2.Make
:执行mingw32-make.exe
读取构建目录中Makefile
进行编译,通过编译最终生成可执行文件。
回到项目,点击绿色运行按钮或按快捷键Ctrl+R运行项目,点击窗口下方的【编译输出】就能够看到类似这样的详细步骤:
ruby
16:12:38: 为项目First_1执行步骤 ...
16:12:38: 正在启动 "D:\exploit\Qt\6.7.3\mingw_64\bin\qmake.exe" F:\Qt\2025_4\First_1\First_1.pro -spec win32-g++ "CONFIG+=debug" "CONFIG+=qml_debug"
Info: creating stash file F:\Qt\2025_4\First_1\build\Desktop_Qt_6_7_3_MinGW_64_bit-Debug\.qmake.stash
16:12:38: 进程"D:\exploit\Qt\6.7.3\mingw_64\bin\qmake.exe"正常退出。
16:12:38: 正在启动 "D:\exploit\Qt\Tools\mingw1120_64\bin\mingw32-make.exe" -f F:/Qt/2025_4/First_1/build/Desktop_Qt_6_7_3_MinGW_64_bit-Debug/Makefile qmake_all
mingw32-make: Nothing to be done for 'qmake_all'.
16:12:38: 进程"D:\exploit\Qt\Tools\mingw1120_64\bin\mingw32-make.exe"正常退出。
16:12:38: 正在启动 "D:\exploit\Qt\Tools\mingw1120_64\bin\mingw32-make.exe" -j16
D:/exploit/Qt/Tools/mingw1120_64/bin/mingw32-make -f Makefile.Debug
mingw32-make[1]: Entering directory 'F:/Qt/2025_4/First_1/build/Desktop_Qt_6_7_3_MinGW_64_bit-Debug'
D:\exploit\Qt\6.7.3\mingw_64\bin\uic.exe ..\..\mainwindow.ui -o ui_mainwindow.h
g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I../../../First_1 -I. -ID:/exploit/Qt/6.7.3/mingw_64/include -ID:/exploit/Qt/6.7.3/mingw_64/include/QtWidgets -ID:/exploit/Qt/6.7.3/mingw_64/include/QtGui -ID:/exploit/Qt/6.7.3/mingw_64/include/QtCore -Idebug -I. -I/include -ID:/exploit/Qt/6.7.3/mingw_64/mkspecs/win32-g++ -o debug\main.o ..\..\main.cpp
g++ -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -dM -E -o debug\moc_predefs.h D:\exploit\Qt\6.7.3\mingw_64\mkspecs\features\data\dummy.cpp
g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I../../../First_1 -I. -ID:/exploit/Qt/6.7.3/mingw_64/include -ID:/exploit/Qt/6.7.3/mingw_64/include/QtWidgets -ID:/exploit/Qt/6.7.3/mingw_64/include/QtGui -ID:/exploit/Qt/6.7.3/mingw_64/include/QtCore -Idebug -I. -I/include -ID:/exploit/Qt/6.7.3/mingw_64/mkspecs/win32-g++ -o debug\mainwindow.o ..\..\mainwindow.cpp
D:\exploit\Qt\6.7.3\mingw_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN --include F:/Qt/2025_4/First_1/build/Desktop_Qt_6_7_3_MinGW_64_bit-Debug/debug/moc_predefs.h -ID:/exploit/Qt/6.7.3/mingw_64/mkspecs/win32-g++ -IF:/Qt/2025_4/First_1 -ID:/exploit/Qt/6.7.3/mingw_64/include -ID:/exploit/Qt/6.7.3/mingw_64/include/QtWidgets -ID:/exploit/Qt/6.7.3/mingw_64/include/QtGui -ID:/exploit/Qt/6.7.3/mingw_64/include/QtCore -I. -ID:/exploit/Qt/Tools/mingw1120_64/lib/gcc/x86_64-w64-mingw32/11.2.0/include/c++ -ID:/exploit/Qt/Tools/mingw1120_64/lib/gcc/x86_64-w64-mingw32/11.2.0/include/c++/x86_64-w64-mingw32 -ID:/exploit/Qt/Tools/mingw1120_64/lib/gcc/x86_64-w64-mingw32/11.2.0/include/c++/backward -ID:/exploit/Qt/Tools/mingw1120_64/lib/gcc/x86_64-w64-mingw32/11.2.0/include -ID:/exploit/Qt/Tools/mingw1120_64/lib/gcc/x86_64-w64-mingw32/11.2.0/include-fixed -ID:/exploit/Qt/Tools/mingw1120_64/x86_64-w64-mingw32/include ..\..\mainwindow.h -o debug\moc_mainwindow.cpp
g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I../../../First_1 -I. -ID:/exploit/Qt/6.7.3/mingw_64/include -ID:/exploit/Qt/6.7.3/mingw_64/include/QtWidgets -ID:/exploit/Qt/6.7.3/mingw_64/include/QtGui -ID:/exploit/Qt/6.7.3/mingw_64/include/QtCore -Idebug -I. -I/include -ID:/exploit/Qt/6.7.3/mingw_64/mkspecs/win32-g++ -o debug\moc_mainwindow.o debug\moc_mainwindow.cpp
g++ -Wl,-subsystem,windows -mthreads -o debug\First_1.exe debug/main.o debug/mainwindow.o debug/moc_mainwindow.o D:\exploit\Qt\6.7.3\mingw_64\lib\libQt6Widgets.a D:\exploit\Qt\6.7.3\mingw_64\lib\libQt6Gui.a D:\exploit\Qt\6.7.3\mingw_64\lib\libQt6Core.a -lmingw32 D:\exploit\Qt\6.7.3\mingw_64\lib\libQt6EntryPoint.a -lshell32
mingw32-make[1]: Leaving directory 'F:/Qt/2025_4/First_1/build/Desktop_Qt_6_7_3_MinGW_64_bit-Debug'
16:12:46: 进程"D:\exploit\Qt\Tools\mingw1120_64\bin\mingw32-make.exe"正常退出。
16:12:46: Elapsed time: 00:08.
内容很多,一点点讲解。
1.运行qmake
生成Makefile
日志:
arduino
正在启动 "D:\exploit\Qt\6.7.3\mingw_64\bin\qmake.exe" F:\Qt\2025_4\First_1\First_1.pro -spec win32-g++ "CONFIG+=debug" "CONFIG+=qml_debug"
解释:
- 执行
qmake.exe
程序读取你的.pro
文件,根据里面的配置(源文件、头文件、UI文件、编译选项等),生成一份 Makefile。 - 参数
-spec win32-g++
告诉 qmake 使用 MinGW 的编译规则(而不是MSVC等)。 CONFIG+=debug
是生成调试版(会带调试信息、不开启优化,适合开发阶段)。CONFIG+=qml_debug
是为了支持 QML 调试(你的项目如果用到了 QML,就能远程调试了)。- 生成中途,创建了一个
.qmake.stash
文件:缓存一些变量,加快下次构建速度。
2.运行 mingw32-make qmake_all
(检查 Makefile)
日志:
arduino
正在启动 "mingw32-make.exe" -f Makefile qmake_all
输出:
vbnet
mingw32-make: Nothing to be done for 'qmake_all'.
解释:
qmake_all
是一个Makefile里的伪目标,意思是"如果有文件变化,重新跑qmake"。- 这里输出 "Nothing to be done" ,说明 文件没有变化,不需要重新生成 Makefile。
3.开始实际编译:mingw32-make -j16
日志:
arduino
正在启动 "mingw32-make.exe" -j16
解释:
-j16
表示用 16个线程并行加速编译(你的机器应该有多核CPU)。
之后就是实际的编译和链接过程了,细分成几步:
4.Qt
工具链生成辅助代码(uic
、moc
)
(1)uic
处理UI文件
日志:
uic.exe mainwindow.ui -o ui_mainwindow.h
解释:
uic
(User Interface Compiler) 把.ui
文件(Qt Designer设计的窗口)转换成对应的 C++ 头文件ui_mainwindow.h
。- 这个头文件里就是窗口里的控件定义,比如按钮、文本框等。
(2)编译源文件
日志:
css
g++ -c ... -o debug\main.o ..\..\main.cpp
解释:
-c
:只编译不链接。
把 main.cpp
编译为 main.o
(目标文件)。
(3)预处理宏定义文件
日志:
arduino
g++ -fno-keep-inline-dllexport -g ... -dM -E -o debug\moc_predefs.h dummy.cpp
解释:
- 收集当前编译环境的宏定义,比如系统平台、编译器特性,供后续的
moc
使用。 -dM -E
:只做预处理并输出宏定义,用来辅助moc
。moc_predefs.h
是生成 moc 代码时需要包含的宏环境。
(4)moc
处理信号与槽
日志:
moc.exe mainwindow.h -o moc_mainwindow.cpp
解释:
moc
(Meta-Object Compiler) 处理你的.h
文件,分析类中定义的signals
、slots
、Q_OBJECT
宏。- 生成
moc_mainwindow.cpp
,它里面是 Qt 的信号槽机制所需要的底层代码。
5.编译源代码
(1)编译你的.cpp
文件
日志:
css
g++ -c main.cpp -o debug\main.o
g++ -c mainwindow.cpp -o debug\mainwindow.o
解释:
-c
表示只编译成目标文件.o
,不会链接成可执行文件。- 每个
.cpp
→.o
,独立编译。 - 加了很多
-I
参数,是告诉编译器哪些目录里有头文件。
(2)编译辅助生成.cpp
文件
日志:
lua
g++ -c moc_mainwindow.cpp -o debug\moc_mainwindow.o
解释:
moc
生成的代码也要编译成.o
文件,才能后面一起链接。
6.链接生成最终的.exe
日志:
lua
g++ -Wl,-subsystem,windows -mthreads -o debug\First_1.exe ...
解释:
链接(Link)阶段 ,把所有 .o
文件合并起来,生成最终可执行文件 First_1.exe
。
链接了必要的 Qt库,比如:
libQt6Widgets.a
libQt6Gui.a
libQt6Core.a
libQt6EntryPoint.a
还有系统库,比如 -lshell32
。
-Wl,-subsystem,windows
参数告诉 Windows,这是个 窗口程序(没有控制台窗口弹出)。
7.构建完成,退出
日志:
lua
进程"mingw32-make.exe"正常退出。
Elapsed time: 00:08
解释:
- 构建过程没有错误,成功生成了
debug\First_1.exe
。 - 整个过程用了 8秒钟,效率还可以。
3.3 构建流程图
4.总结
本文详细分析了 Qt 项目在构建过程中的各个步骤,包括 qmake
生成 Makefile、uic
处理 UI 文件、moc
处理头文件、g++
编译与链接过程。通过一步步解读实际编译日志,理清了 Qt 工程背后复杂但有序的编译流程,并绘制了对应的流程图,方便理解和记忆。