为什么选择 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_SHOWTIP 和 NOTIFYICON_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-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)