VS2017 编译 Qt 5.7.1
2024-09-07 18:04:04

为什么选择 VS2017

Visual Studio 2017 是最后一个支持编译 XP 程序的工具集了,而且支持 C++17,这样可以用上更多的新特性,提高开发效率。

为什么选择 Qt 5.7.1

因为这是最后一个支持 XP 的大版本中的最后一个小版本,再往上的版本就开始使用了 Vista 以后才有的 API 和数据结构。
在编译 Qt 时有一个配置参数target,它有一个唯一值是xp,这个选项在 5.7.0 版中还存在,但在 5.7.1 版中就移除了。

1
2
3
-target ............ Set target OS version. Currently the only valid value 
is 'xp' for targeting Windows XP.
MSVC >= 2012 targets Windows Vista by default.

而这个参数似乎也只是影响/SUBSYSTEM链接参数而已(将其值设置为 5.01 以便在 XP 系统上运行),而这个参数我们可以通过其他方式修改。

编译 qtbase

首先去 https://download.qt.io/new_archive/qt/5.7/5.7.1/submodules/ 下载基础模块 qtbase-opensource-src-5.7.1.7z
解压缩后在源码根目录下创建一个子目录_build作为编译输出目录。启动 VS2017 命令行工具,执行配置:

1
..\configure.bat -prefix "C:/Qt/Qt5.7.1-vc141-x86" -platform win32-msvc2015 -confirm-license -opensource -debug-and-release -shared -nomake examples -nomake tests -no-style-fusion -no-directwrite -no-angle -opengl desktop -mp

参数可以根据自身需求调整,但是-no-directwrite必须设置。

1
-no-directwrite .... Do not build support for DirectWrite font rendering.

这个选项是关于字体渲染的(DirectWrite),但是这个技术只在 Vista 以后的系统上受支持,所以我们要禁用它。


另外值得注意的一个是-platform选项设置的是win32-msvc2015,为什么不是win32-msvc2017呢?因为从 Qt 5.7.0 开始已经没有提供win32-msvc2017的设置了

编译过程中的一些条件分支最高只验证到 VS2015,所以我们选择一个最高的版本即可。

编辑 msvc-desktop.conf

配置完成后先关闭 VS 命令行窗口。接着编辑mkspecs\common\msvc-desktop.conf文件。
找到这行:

1
DEFINES += UNICODE WIN32

修改为

1
DEFINES += UNICODE WIN32 WINVER=0x0501 _WIN32_WINNT=0x0501 _USING_V110_SDK71_
  • WINVER_WIN32_WINNT 决定支持的最低操作系统,0x0501表示 32 位 XP 系统。
  • _USING_V110_SDK71_ 则表示使用 SDK 7.1,这是最后一个支持 XP 的 SDK。


接着修改/SUBSYSTEM链接参数为5.01但注意,在 Qt 5.7.1 版本中,不可以这样改:

1
2
QMAKE_LFLAGS_CONSOLE    = /SUBSYSTEM:CONSOLE,5.01
QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01

网上很多文章中都是这样改的,是因为 Qt 版本不同,5.7.1 版本这样改是没有效果的。
我们以/SUBSYSTEM:WINDOWS作为关键词搜索,可以在mkspecs\common\msvc-base.conf文件中找到这么一段:

1
2
3
4
5
6
7
8
greaterThan(MSC_VER, 1699) {
# Visual Studio 2012 (11.0) / Visual C++ 17.0 and up
QMAKE_CXXFLAGS_EXCEPTIONS_OFF = -D_HAS_EXCEPTIONS=0
QMAKE_LFLAGS_CONSOLE = /SUBSYSTEM:CONSOLE@QMAKE_SUBSYSTEM_SUFFIX@
QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS@QMAKE_SUBSYSTEM_SUFFIX@
QT_CONFIG += c++11
CONFIG += c++11
}

最老的 VS2017 版本的 MSC_VER 值是 1910 了,所以这段配置会生效。
这里用到了QMAKE_SUBSYSTEM_SUFFIX变量,而这个变量的初始化在mkspecs\features\win32\qt_config.prf文件中:

1
2
3
4
5
6
7
8
9
10
load(qt_config)

equals(QMAKE_TARGET_OS, xp) {
# http://blogs.msdn.com/b/vcblog/archive/2012/10/08/10357555.aspx?PageIndex=3
equals(QT_ARCH, x86_64) {
QMAKE_SUBSYSTEM_SUFFIX = ,5.02
} else {
QMAKE_SUBSYSTEM_SUFFIX = ,5.01
}
}

变量QMAKE_TARGET_OS正是前面说的在 Qt 5.7.0 以前支持的配置参数target的值,由于在 5.7.1 中移除了该选项,导致QMAKE_SUBSYSTEM_SUFFIX为空,同时会导致/SUBSYSTEM参数为空。而当/SUBSYSTEM的值为空时,将会使用6.0作为默认值,这样编译出的程序在 XP 系统上运行就会提示不是有效的Win32程序
解决办法就是给QMAKE_SUBSYSTEM_SUFFIX赋值:

1
2
3
QMAKE_LFLAGS_CONSOLE    = /SUBSYSTEM:CONSOLE
QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS
QMAKE_SUBSYSTEM_SUFFIX = ,5.01

编译源码

由于使用了 SDK 7.1,所以需要在 CMD 环境中配置一下头文件、库文件的搜索路径,在_build目录下创建一个build.bat文件并执行,内容如下:

1
2
3
4
5
6
7
@echo off
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Auxiliary\Build\vcvars32.bat"
set PATH=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH%
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE%
set LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%LIB%
nmake
nmake install

开始编译后不久会碰到第一个错误:

1
2
3
src\widgets\util\qsystemtrayicon_win.cpp(270): error C2065: “NIF_SHOWTIP”: 未声明的标识符
src\widgets\util\qsystemtrayicon_win.cpp(283): error C2065: “NIF_SHOWTIP”: 未声明的标识符
src\widgets\util\qsystemtrayicon_win.cpp(327): error C2065: “NOTIFYICON_VERSION_4”: 未声明的标识符

NIF_SHOWTIPNOTIFYICON_VERSION_4 是 Vista 后才有的常量,我们可以手动加上,定位到 79 行可以看到针对 MinGW 编译时会定义:

1
2
3
4
5
6
7
#ifdef Q_CC_MINGW
# define NIN_SELECT (WM_USER + 0)
# define NIN_BALLOONTIMEOUT (WM_USER + 4)
# define NIN_BALLOONUSERCLICK (WM_USER + 5)
# define NIF_SHOWTIP 0x00000080
# define NOTIFYICON_VERSION_4 4
#endif

我们只需要在后面定义即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifdef Q_CC_MINGW
# define NIN_SELECT (WM_USER + 0)
# define NIN_BALLOONTIMEOUT (WM_USER + 4)
# define NIN_BALLOONUSERCLICK (WM_USER + 5)
# define NIF_SHOWTIP 0x00000080
# define NOTIFYICON_VERSION_4 4
#endif

#ifndef NIF_SHOWTIP
#define NIF_SHOWTIP 0x00000080
#endif

#ifndef NOTIFYICON_VERSION_4
#define NOTIFYICON_VERSION_4 4
#endif


接着会碰到第二个编译错误:
src\testlib\qtestcase.cpp(1578): error C3861: “RtlCaptureStackBackTrace”: 找不到标识符
定位到问题处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static LONG WINAPI windowsFaultHandler(struct _EXCEPTION_POINTERS *exInfo)
{
enum { maxStackFrames = 100 };
char appName[MAX_PATH];
if (!GetModuleFileNameA(NULL, appName, MAX_PATH))
appName[0] = 0;
const int msecsFunctionTime = qRound(QTestLog::msecsFunctionTime());
const int msecsTotalTime = qRound(QTestLog::msecsTotalTime());
const void *exceptionAddress = exInfo->ExceptionRecord->ExceptionAddress;
printf("A crash occurred in %s.\n"
"Function time: %dms Total time: %dms\n\n"
"Exception address: 0x%p\n"
"Exception code : 0x%lx\n",
appName, msecsFunctionTime, msecsTotalTime,
exceptionAddress, exInfo->ExceptionRecord->ExceptionCode);

DebugSymbolResolver resolver(GetCurrentProcess());
if (resolver.isValid()) {
DebugSymbolResolver::Symbol exceptionSymbol = resolver.resolveSymbol(DWORD64(exceptionAddress));
if (exceptionSymbol.name) {
printf("Nearby symbol : %s\n", exceptionSymbol.name);
delete [] exceptionSymbol.name;
}
void *stack[maxStackFrames];
fputs("\nStack:\n", stdout);
const unsigned frameCount = CaptureStackBackTrace(0, DWORD(maxStackFrames), stack, NULL);
for (unsigned f = 0; f < frameCount; ++f) {
DebugSymbolResolver::Symbol symbol = resolver.resolveSymbol(DWORD64(stack[f]));
if (symbol.name) {
printf("#%3u: %s() - 0x%p\n", f + 1, symbol.name, (const void *)symbol.address);
delete [] symbol.name;
} else {
printf("#%3u: Unable to obtain symbol\n", f + 1);
}
}
}

fputc('\n', stdout);
fflush(stdout);

return EXCEPTION_EXECUTE_HANDLER;
}

CaptureStackBackTrace 的实际定义是 RtlCaptureStackBackTrace 函数。它是在kernel32.lib中导出的,所以我们可以添加几行代码手动导入它:

1
2
3
4
5
6
extern "C" __declspec(dllimport) USHORT WINAPI RtlCaptureStackBackTrace(
__in ULONG FramesToSkip,
__in ULONG FramesToCapture,
__out_ecount(FramesToCapture) PVOID *BackTrace,
__out_opt PULONG BackTraceHash
);

最后去下载一个 QtCreator 并导入编译好的 Qt 即可。

安装 qttools

qttools 模块包含设计器、语言家、助手等工具,这也是开发时必须要有的东西。下载 qttools-opensource-src-5.7.1.7z 并解压。
接着用前面编译好的qmake.exe来编译 qttools 模块,创建一个build.bat文件,内容如下:

1
2
3
4
5
6
7
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Auxiliary\Build\vcvars32.bat"
set PATH=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH%
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE%
set LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%LIB%
C:\Qt\Qt5.7.1-vc141-x86\bin\qmake.exe -makefile -spec win32-msvc2015
nmake
nmake install

等待编译、安装完即可。

编译语言包

安装好 qttools 后随便打开一个工具运行一下,发现软件都是英文的。

因为源码是不包含语言包的,我们要手动下载语言包编译。
https://download.qt.io/new_archive/qt/5.7/5.7.1/submodules/ 下载 qttranslations-opensource-src-5.7.1.7z 文件。

接着用前面编译好的qmake.exe来编译 qttranslations 模块,创建一个build.bat文件,内容如下:

1
2
3
4
5
6
7
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Auxiliary\Build\vcvars32.bat"
set PATH=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin;%PATH%
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;%INCLUDE%
set LIB=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib;%LIB%
C:\Qt\Qt5.7.1-vc141-x86\bin\qmake.exe -makefile -spec win32-msvc2015
nmake
nmake install

等待编译、安装完即可。

编译文档

文档是随着模块源码一起的,所以每个模块的文档要单独编译。回到前面 qtbase 模块的_build目录,启动 VS2017 命令行工具依次执行:

1
2
nmake docs
nmake install_docs

编译安装完成后,再次打开 Qt 助手工具,就能看到文档了。

发布验证


一个最简单的 hello world 窗口程序需要四个 dll 文件,并按照此目录结构部署即可。

1
2
3
4
5
6
app.exe
Qt5Core.dll
Qt5Gui.dll
Qt5Widgets.dll
platforms/
└── qwindows.dll

大陆地区镜像站点

当网络不好时,可以从国内的镜像站点下载相关资源

阿里云:https://mirrors.aliyun.com/qt/
清华大学:https://mirrors.tuna.tsinghua.edu.cn/qt/
中国科学技术大学:http://mirrors.ustc.edu.cn/qtproject/

相关阅读

Qt 5.6 doesn’t compatible with Windows XP
QT5.7.0在win10下使用visual studio 2015编译(目标平台 xp)

上一页
2024-09-07 18:04:04
下一页