在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.alibQt6Gui.alibQt6Core.alibQt6EntryPoint.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 工程背后复杂但有序的编译流程,并绘制了对应的流程图,方便理解和记忆。