QT备忘录之九:静态编译Qt5.6.3并尝试解决库版本冲突
- 再创世纪·代码厨房
- 24天前
- 243热度
- 0评论
我打算保留一个支持XP的Qt静态编译版本,毕竟国内还有不少场景要求必须支持windows XP。我也说不清楚在AI年代这到底是幸还是不幸。于是我又把十年前干过的事情重新又干了一遍,同时尝试解决一下Qt和其他库依赖冲突的问题。只不过这次是Qt 5.6.3。【本文的部分调研和数据整理工作借助AI工具完成】
0. 为啥是5.6.3?
VS2010是原生支持生成XP程序的最后一个VS版本(后续的VS使用形如v1xx_xp的特殊编译器形式提供;据调查,使用这种方式生成的程序在XP运行容易出现一些奇怪的问题)。
Qt官方编译的最后一个vs2010版本是5.5.1,
源码至5.8.0都保留了vs2010的mkspecs。
然而5.8.0在configure阶段就报错,说编译器不支持c++11:
5.7.0也是在configure阶段就报错,生成qmake失败:
大概看了一下Qt5.7.0中qmake的源码。许多地方用了c++11的语法,VS2010不支持,当然生成不出来了。理论上讲,每一处皆可去c++11化,只不过那样工作量太大,似乎没有必要。
于是Qt5.6.3便是实际支持VS2010的最后一个版本。
1. 解决库冲突的一种尝试
Qt作为一个跨平台开发框架,依赖大量第三方开源库实现具体的功能(zlib、libjpeg、libpng、libtiff、openssl等等)。过往的开发过程中,曾经遇到过由于opencv与Qt(两者都是静态编译,MT/MTd)依赖的libpng库版本不同,导致联合编程时cv::Mat加载png文件有概率失败。这个事情后来只能通过QImage加载图像文件再内存拷贝传给cv::Mat来规避。最近想起来这堆屎山,抽空做了一些调研,可能有办法解决。可以想见,除了opencv还有可能跟别的库/组件起冲突,最好的办法是把这些库都独立出来管理,而不是直接编进Qt环境中。
1.1 configure中的线索
使用configure -help可以获取当前版本的configure语法。【此版本的configure中,带“*”的为默认项,带“+”的为待评估的默认项(试编译,不满足条件则相当于-no)】
数据库部分:全部使用-plugin。此策略将数据库驱动作为外部库,需要时候再加入。其中sqlite还可以选择用-system的方式,使用外部编译的库文件,而不是直接编译源码;其他数据库由对应的sdk提供库文件。【新版本的Qt源码,数据库部分默认使用的就是plugin方式】
opengl部分:使用 -opengl es2(默认值)。此策略通过 ANGLE (Almost Native Graphics Layer Engine)将 OpenGL 调用转换为 DirectX 调用。目前仍坚持使用windowsXP的电脑普遍比较古老,而DirectX 在 Windows 上的驱动质量普遍比 OpenGL 更稳定、更广泛。开发环境需要预先安装好DirectX;发布时需带上 libEGL.dll、libGLESv2.dll 和 d3dcompiler_47.dll 等动态链接库。毕竟古代环境下,稳定才是最重要的。
第三方库部分:全部使用-system。此策略将可剥离的第三方库全部使用外部编译,Qt仅在需要时链接相应的库文件。外部编译的第三方库可以与其他环境共享,且存在升版本的可能。
警告处理部分:使用-no-warnings-are-errors。除非你想处理各种库代码中的不规范写法(尤其是早期版本的库)
openssl部分:使用-openssl(默认值),不使用-linked。如此只在需要的场景下(比如https通信),带上openssl的库文件即可。
1.2 第三方库的版本要求
接下来在Qt5.6.3源码包中找到上面被标记为-system的第三方库的版本。理论上讲,使用相同的版本重新编译,Qt再来链接就不会产生任何问题。后续这些库升版测试也很容易,替换库文件即可,Qt环境不需要变动。结果如下:
| 库名 | Qt5.6.3内置版本 | 备注 |
|---|---|---|
| zlib | 1.2.5 | 压缩/解压缩 |
| pcre | 8.39 | 正则表达式 |
| libjpeg | 8c | jpeg格式 |
| libpng | 1.6.20 | png格式 |
| harfbuzz(-ng) | 1.0.6 | 文本整形,-ng是替代版本 |
| freetype | 2.6.1 | 字体渲染,依赖zlib和pcre |
| sqlite(3) | 3.8.10.2 | sqlite3数据库 |
1.3 修改Qt源码中关于第三方库调用的配置
此部分内容全网暂无详细的解析文章,本文应是首发。跟我一起抽丝剥茧,看一下Qt环境是如何一步一步被构建出来的。
首先从configure脚本开始。根目录的configure直接指向了\qtbase\configure.bat。
然后在这个脚本中调用了configure.exe,并带上了在根目录给它的参数。
在qtbase\tools\configure找到configure.exe的工程configure.pro,看看它都干了啥。关键都在这个函数里:
// qtbase\tools\configure\main.cpp
int runConfigure( int argc, char** argv )
{
Configure app( argc, argv );
if (!app.isOk())
return 3;
app.parseCmdLine();
app.validateArgs();
if (!app.isOk())
return 3;
if( app.displayHelp() )
return 1;
// Read license now, and exit if it doesn't pass.
// This lets the user see the command-line options of configure
// without having to load and parse the license file.
app.readLicense();
if (!app.isOk())
return 3;
// Source file with path settings. Needed by qmake.
app.generateQConfigCpp();
// Bootstrapped includes. Needed by qmake.
app.generateHeaders();
if (!app.isOk())
return 3;
// Bootstrap qmake. Needed by config tests.
app.buildQmake();
if (!app.isOk())
return 3;
// Generate qdevice.pri
app.generateQDevicePri();
if (!app.isOk())
return 3;
// Prepare the config test build directory.
app.prepareConfigTests();
if (!app.isOk())
return 3;
// Auto-detect modules and settings.
app.autoDetection();
// After reading all command-line arguments, and doing all the
// auto-detection, it's time to do some last minute validation.
// If the validation fails, we cannot continue.
if (!app.verifyConfiguration())
return 3;
app.generateOutputVars();
if( !app.isDone() )
app.generateCachefile();
if( !app.isDone() )
app.generateConfigfiles();
if (!app.isDone())
app.generateQConfigPri();
if (!app.isDone())
app.displayConfig();
if( !app.isDone() )
app.generateMakefiles();
if( !app.isDone() )
app.showSummary();
if( !app.isOk() )
return 2;
return 0;
}
Configure类的关键函数列举如下:
- parseCmdLine():解析了命令行参数,并存入一个QMap<QString,QString>字典中;
- generateQConfigCpp():生成 src/corelib/global/qconfig.cpp,包含安装路径、许可证信息等字符串常量。
- buildQmake():在构建目录下生成的 Makefile,而后编译 qmake。
- autoDetection():自动检测系统环境,包括:
- CPU 架构(调用 detectArch())
- C++ 标准支持(C++11/14/1z)
- 原子操作支持(ATOMIC64)
- 第三方库(ZLIB, PCRE, ICU, JPEG, PNG, Freetype, HarfBuzz, OpenSSL, DBus 等)
- 图形后端(ANGLE, OpenGL, OpenVG)
- SQL 驱动(MySQL, PostgreSQL, ODBC, OCI 等)
- 系统特性(inotify, eventfd, cups, iconv 等)
- verifyConfiguration():检查配置冲突(如 -static-runtime 与 -shared 不能同时使用),并给出警告或错误。
- generateCachefile():生成 mkspecs/qmodule.pri,定义 QT_BUILD_PARTS、QT_SKIP_MODULES 等,并写入 CONFIG 值。
- generateConfigfiles():生成 src/corelib/global/qconfig.h,定义大量 QT_NO_* 和 QT_* 宏,控制编译时功能开关。
- generateQConfigPri():生成 mkspecs/qconfig.pri,定义 QT_CONFIG、QT_ARCH 等变量,供 qmake 使用。
- generateMakefiles():在构建目录运行 qmake,生成最终的 Makefile(包括各级子目录)。
其中全局配置QT_CONFIG(qconfig.pri)包含了configure配置的所有-system配置项,形如:
QT_CONFIG += minimal-config small-config medium-config large-config full-config debug static static_runtime system-zlib angle gif jpeg system-jpeg png system-png system-freetype system-harfbuzz accessibility opengl opengles2 egl ssl openssl dbus audio-backend directwrite native-gestures qpa concurrent
sql-plugins(qmodule.pri)中包含了configure配置的所有-plugin-sql配置项,存在全局配置sql-plugins中,形如:
sql-plugins += mysql odbc oci psql tds db2 sqlite sqlite2 ibase
在Qt源码中搜索system-zlib,有如下几处调用:
其中qtbase\src\3rdparty\zlib_dependency.pri对于整个代码的理解很有帮助。其源码如下:
#zlib_dependency.pri
# zlib dependency satisfied by bundled 3rd party zlib or system zlib
contains(QT_CONFIG, system-zlib) {
if(unix|mingw):LIBS_PRIVATE += -lz
else {
isEmpty(ZLIB_LIBS): LIBS += zdll.lib
else: LIBS += $$ZLIB_LIBS
}
} else {
INCLUDEPATH += $$PWD/zlib
!no_core_dep {
CONFIG += qt
QT_PRIVATE += core
}
}
很好理解。如果QT_CONFIG中有system-zlib,从指定的库文件加载zlib。考虑到外部独立编译的zlib库可以确保Windows和linux库名称一致,且有可能需要调试,我打算将这些用到zlib的地方,针对debug和release分别指定不同的库文件名(不硬编码库完整路径)。此处ZLIB_LIBS变量可以指定库名称,但是不能区分debug和release,我不打算使用此参数。其次库文件名为了避免错误加载其他不受控的版本,所有单独编译的静态第三方库都加上sl后缀,debug再加_d后缀。例如zlib编译出来的库应是libzsl.lib(release)和libzsl_d.lib(debug)。涉及修改的文件如下:
- qt3d\src\3rdparty\assimp\assimp.pri
# assimp.pri
contains(QT_CONFIG, system-zlib):!if(cross_compile:host_build) {
# if (unix|mingw): LIBS += -lz
# else: LIBS += zdll.lib
if(unix|mingw) {
CONFIG(debug, debug|release) {
LIBS += -lzsl_d
} else {
LIBS += -lzsl
}
} else {
CONFIG(debug, debug|release) {
LIBS += -llibzsl_d
} else {
LIBS += -llibzsl
}
} }
} else {
QT_PRIVATE += zlib-private
}
- qtbase\src\3rdparty\zlib_dependency.pri
# zlib_dependency.pri (qtbase)
# zlib dependency satisfied by bundled 3rd party zlib or system zlib
contains(QT_CONFIG, system-zlib) {
# if(unix|mingw):LIBS_PRIVATE += -lz
# else {
# isEmpty(ZLIB_LIBS): LIBS += zdll.lib
# else: LIBS += $$ZLIB_LIBS
# }
if(unix|mingw) {
CONFIG(debug, debug|release) {
LIBS += -lzsl_d
} else {
LIBS += -lzsl
}
} else {
CONFIG(debug, debug|release) {
LIBS += -llibzsl_d
} else {
LIBS += -llibzsl
}
}
} else {
INCLUDEPATH += $$PWD/zlib
!no_core_dep {
CONFIG += qt
QT_PRIVATE += core
}
}
- qtimageformats\src\3rdparty\zlib_dependency.pri
# zlib_dependency.pri (qtimageformats)
# zlib dependency satisfied by bundled 3rd party zlib or system zlib
contains(QT_CONFIG, system-zlib) {
# unix|mingw: LIBS_PRIVATE += -lz
# else {
# isEmpty(ZLIB_LIBS): LIBS += zdll.lib
# else: LIBS += $$ZLIB_LIBS
# }
if(unix|mingw) {
CONFIG(debug, debug|release) {
LIBS += -lzsl_d
} else {
LIBS += -lzsl
}
} else {
CONFIG(debug, debug|release) {
LIBS += -llibzsl_d
} else {
LIBS += -llibzsl
}
}
} else {
QT_PRIVATE += zlib-private
}
- qtsvg\src\svg\svg.pro
# svg.pro
contains(QT_CONFIG, system-zlib) {
# if(unix|mingw): LIBS_PRIVATE += -lz
# else {
# isEmpty(ZLIB_LIBS): LIBS += zdll.lib
# else: LIBS += $$ZLIB_LIBS
# }
if(unix|mingw) {
CONFIG(debug, debug|release) {
LIBS += -lzsl_d
} else {
LIBS += -lzsl
}
} else {
CONFIG(debug, debug|release) {
LIBS += -llibzsl_d
} else {
LIBS += -llibzsl
}
}
} else {
QT_PRIVATE += zlib-private
}
其他依赖zlib的地方,基本上都是一个写法:
contains(QT_CONFIG, zlib) {
include($$PWD/../../3rdparty/zlib.pri)
} else {
CONFIG += no_core_dep
include($$PWD/../../3rdparty/zlib_dependency.pri)
}
也此处实现的就是根据configure中system-zlib或者qt-zlib配置,分别选择从代码或者外部库加载zlib。
system-pcre是通过qtbase\src\3rdparty\pcre_dependency.pri管理的,需要特别注意的是pcre库使用静态链接时需要加上PCRE_STATIC。此文件修改如下:
# pcre_dependency.pri
pcre {
win32: DEFINES += PCRE_STATIC
INCLUDEPATH += $$PWD/pcre
LIBS_PRIVATE += -L$$QT_BUILD_TREE/lib -lqtpcre$$qtPlatformTargetSuffix()
} else {
# LIBS_PRIVATE += -lpcre16
win32 {
DEFINES += PCRE_STATIC
CONFIG(debug, debug|release) {
LIBS_PRIVATE += -llibpcre16sl_d
} else {
LIBS_PRIVATE += -llibpcre16sl
}
} else {
CONFIG(debug, debug|release) {
LIBS_PRIVATE += -lpcre16sl_d
} else {
LIBS_PRIVATE += -lpcre16sl
}
}
}
system-jpeg是通过qtbase\src\gui\image\qjpeghandler.pri管理的。此文件修改如下:,
# qjpeghandler.pri
# common to plugin and built-in forms
HEADERS += $$PWD/qjpeghandler_p.h
SOURCES += $$PWD/qjpeghandler.cpp
contains(QT_CONFIG, system-jpeg) {
# msvc: \
# LIBS += libjpeg.lib
# else: \
# LIBS += -ljpeg
win32 {
CONFIG(debug, debug|release) {
LIBS += -llibjpegsl_d
} else {
LIBS += -llibjpegsl
}
} else {
CONFIG(debug, debug|release) {
LIBS += -ljpegsl_d
} else {
LIBS += -ljpegsl
}
}
} else {
include($$PWD/../../3rdparty/libjpeg.pri)
}
system-png是通过qtbase\src\3rdparty\png_dependency.pri管理的。此文件修改如下:,
# png_dependency.pri
contains(QT_CONFIG, system-png) {
unix|mingw {
!contains(QT_CONFIG, no-pkg-config) {
CONFIG += link_pkgconfig
PKGCONFIG_PRIVATE += libpng
} else {
# LIBS_PRIVATE += -lpng
CONFIG(debug, debug|release) {
LIBS += -lpngsl_d
} else {
LIBS += -lpngsl
}
}
} else {
# LIBS += libpng.lib
CONFIG(debug, debug|release) {
LIBS += -llibpngsl_d
} else {
LIBS += -llibpngsl
}
}
} else: contains(QT_CONFIG, png) {
include($$PWD/libpng.pri)
}
system-harfbuzz是通过qtbase\src\3rdparty\harfbuzz_dependency.pri管理的。此文件修改如下:,
# harfbuzz_dependency.pri
contains(QT_CONFIG, harfbuzz) {
INCLUDEPATH += $$PWD/harfbuzz-ng/include
LIBS_PRIVATE += -L$$QT_BUILD_TREE/lib -lqtharfbuzzng$$qtPlatformTargetSuffix()
} else:contains(QT_CONFIG, system-harfbuzz) {
# LIBS_PRIVATE += -lharfbuzz
if(unix|mingw) {
CONFIG(debug, debug|release) {
LIBS_PRIVATE += -lharfbuzzsl_d
} else {
LIBS_PRIVATE += -lharfbuzzsl
}
} else {
CONFIG(debug, debug|release) {
LIBS_PRIVATE += -llibharfbuzzsl_d
} else {
LIBS_PRIVATE += -llibharfbuzzsl
}
}
}
system-freetype是通过qtbase\src\3rdparty\freetype_dependency.pri管理的。源文件依赖了一个动态生成的freetype.pri,此处新增win32分支从而实现直接链接库文件(unix暂时不改)。此文件修改如下:
# freetype_dependency.pri
contains(QT_CONFIG, freetype) {
INCLUDEPATH += $$PWD/freetype/include
LIBS_PRIVATE += -L$$QT_BUILD_TREE/lib -lqtfreetype$$qtPlatformTargetSuffix()
} else:contains(QT_CONFIG, system-freetype) {
# pull in the proper freetype2 include directory
win32 {
CONFIG(debug, debug|release) {
LIBS += -llibfreetypesl_d
} else {
LIBS += -llibfreetypesl
}
} else {
include($$QT_SOURCE_TREE/config.tests/unix/freetype/freetype.pri)
}
此外,还有一处qtbase\mkspecs\features\qpa\basicunixfontdatabase.prf,修改如下:
# basicunixfontdatabase.prf
contains(QT_CONFIG, system-freetype) {
# LIBS_PRIVATE += -lfreetype
win32 {
CONFIG(debug, debug|release) {
LIBS_PRIVATE += -llibfreetypesl_d
} else {
LIBS_PRIVATE += -llibfreetypesl
}
} else {
CONFIG(debug, debug|release) {
LIBS_PRIVATE += -lfreetypesl_d
} else {
LIBS_PRIVATE += -lfreetypesl
}
}
}
无法使用-system独立出来的第三方库,例如libjasper、libwebp等,暂时不做处理。
2. 编译第三方库
2.1 编译zlib 1.2.5
- 从官网下载zlib 1.2.5,解压到一个本地路径(不妨假设解压到F:\zlib-1.2.5)。
- zlibstat工程配置中,取消ZLIB_WINAPI宏。【踩坑后补】
- 使用VS2010启动F:\zlib-1.2.5\contrib\vstudio\vc10\zlibvc.sln解决方案,编译zlibstat工程即可。注意,编译Release版本时选ReleaseWithoutAsam配置(而不是Release配置),两者的区别就在于是否包含了用于加速的汇编代码。既然是对远古环境的兼容,没必要为了这点优化增加额外的隐患(远古版本的库隐患本身就不少……)
- Release(包含汇编优化):启用汇编优化。这通常能获得最佳压缩/解压性能,但是有报告指出官方的汇编代码在某些编译环境下可能存在安全隐患,甚至可能和某些现有二进制文件不兼容,导致程序在运行时内存崩溃。
- ReleaseWithoutAsm(不包含汇编):不使用汇编优化。这是很多开发者推荐的配置,因为它稳定性高,避免了因汇编代码可能引发的内存崩溃问题。
2.2 编译pcre 8.39
- 确保perl环境已正确配置(命令行直接运行perl -v能正确返回版本信息即可。如不确定,不妨重新安装一个最新版)。
- 从官网下载pcre 8.39,解压到一个本地路径(不妨假设解压到F:\pcre-8.39)。
- 使用CMake生成VS2010解决方案,注意选上PCRE_BUILD_PCRE16。Qt需要用到这个版本的库。
- 使用vs2010启动生成的F:\pcre-8.39\build\PCRE.sln,编译pcre16工程即可。
2.3 编译libjpeg 8c
- 从官网下载libjpeg-8c,解压到一个本地路径(不妨假设解压到F:\jpeg-8c)。
- 使用管理员权限启动Visual Studio 命令提示(2010)并cd到F:\jpeg-8c。
- 执行nmake /f makefile.vc setup-v10生成vs2010解决方案。
- 使用vs2010启动生成的F:\jpeg-8c\jpeg.sln,Release配置将“配置属性——C/C++——代码生成——运行库”由MD改为MT,编译生成库文件。
- 手动添加Debug解决方案配置,注意“配置属性——C/C++——代码生成——运行库”配为MTd,其他参数参照一般工程的Debug配置,编译生成库文件。
2.4 编译libpng 1.6.20
- 从官网下载libpng 1.6.20,解压到一个本地路径(不妨假设解压到F:\lpng1620)。
- 使用vs2010启动F:\lpng1620\projects\vstudio\vstudio.sln。
- Debug Library和Release Library配置zlib的头文件文件夹路径,并取消pnglibconf、zlib工程的项目依赖项。
- 使用Debug Library和Release Library配置分别编译生成库文件。
2.5 编译harfbuzz 1.0.6 ⇒ 1.2.0
- 从官网下载的harfbuzz 1.0.6没有包含win32编译相关的配置,不想折腾,选择最接近的支持Win32编译配置的1.2.0进行替代。
- 确保Ragel环境已正确配置(命令行直接运行ragel -v能正确返回版本信息即可。如不确定,不妨执行winget install --id=PolarGoose.Ragel重新安装一个)。
- 从官网下载harfbuzz 1.2.0,解压到一个本地路径(不妨假设解压到F:\harfbuzz-1.2.0)。
- 将win32\config.h.win32.in复制到src,并重命名为config.h。
- 在win32文件夹创建release\Win32\harfbuzz\和debug\Win32\harfbuzz\子文件夹。【踩坑后补】
- harfbuzz-1.2.0\src\hb-uniscribe.cc添加#pragma comment(lib, "usp10.lib")和#pragma comment(lib, "rpcrt4.lib")。【踩坑后补】
- 重命名src\hb-version.h.in为src\hb-version.h,并手动填入版本号信息。
// hb-version.h
HB_BEGIN_DECLS
#define HB_VERSION_MAJOR 1
#define HB_VERSION_MINOR 2
#define HB_VERSION_MICRO 0
#define HB_VERSION_STRING "1.2.0"
#define HB_VERSION_ATLEAST(major,minor,micro) \
((major)*10000+(minor)*100+(micro) <= \
HB_VERSION_MAJOR*10000+HB_VERSION_MINOR*100+HB_VERSION_MICRO)
- 编辑win32\detectenv-msvc.mak文件,将MD改为MT,MDd改为MTd。
# detectenv-msvc.mak
# One may change these items, but be sure to test
# the resulting binaries
!if "$(CFG)" == "release"
CFLAGS_ADD = /MT /O2 /GL /MP
!if "$(VSVER)" != "9"
CFLAGS_ADD = $(CFLAGS_ADD) /d2Zi+
!endif
!else
CFLAGS_ADD = /MTd /Od
!endif
- 编辑win32\build-rules-msvc.mak文件,取消Dll生成相关配置,添加静态库生成相关配置。
# build-rules-msvc.mak
# Rules for building .lib files
# $(CFG)\$(PLAT)\harfbuzz.lib: $(HARFBUZZ_DLL_FILENAME).dll
# $(CFG)\$(PLAT)\harfbuzz-icu.lib: $(HARFBUZZ_ICU_DLL_FILENAME).dll
# $(CFG)\$(PLAT)\harfbuzz-gobject.lib: $(HARFBUZZ_GOBJECT_DLL_FILENAME).dll
# Rules for linking DLLs
# Format is as follows (the mt command is needed for MSVC 2005/2008 builds):
# $(dll_name_with_path): $(dependent_libs_files_objects_and_items)
# link /DLL [$(linker_flags)] [$(dependent_libs)] [/def:$(def_file_if_used)] [/implib:$(lib_name_if_needed)] -out:$@ @<<
# $(dependent_objects)
# <<
# @-if exist $@.manifest mt /manifest $@.manifest /outputresource:$@;2
# $(HARFBUZZ_DLL_FILENAME).dll: config.h $(harfbuzz_dll_OBJS) $(CFG)\$(PLAT)\harfbuzz
# link /DLL $(LDFLAGS) $(HB_DEP_LIBS) /implib:$(CFG)\$(PLAT)\harfbuzz.lib -out:$@ @<<
# $(harfbuzz_dll_OBJS)
# <<
# @-if exist $@.manifest mt /manifest $@.manifest /outputresource:$@;2
# $(HARFBUZZ_ICU_DLL_FILENAME).dll: $(CFG)\$(PLAT)\harfbuzz.lib $(harfbuzz_icu_OBJS) $(CFG)\$(PLAT)\harfbuzz-icu
# link /DLL $(LDFLAGS) $(CFG)\$(PLAT)\harfbuzz.lib $(HB_ICU_DEP_LIBS) /implib:$(CFG)\$(PLAT)\harfbuzz-icu.lib -out:$@ @<<
# $(harfbuzz_icu_OBJS)
# <<
# @-if exist $@.manifest mt /manifest $@.manifest /outputresource:$@;2
# $(HARFBUZZ_GOBJECT_DLL_FILENAME).dll: $(CFG)\$(PLAT)\harfbuzz.lib $(harfbuzz_gobject_OBJS) $(CFG)\$(PLAT)\harfbuzz-gobject
# link /DLL $(LDFLAGS) $(CFG)\$(PLAT)\harfbuzz.lib $(HB_GOBJECT_DEP_LIBS) /implib:$(CFG)\$(PLAT)\harfbuzz-gobject.lib -out:$@ @<<
# $(harfbuzz_gobject_OBJS)
# <<
# @-if exist $@.manifest mt /manifest $@.manifest /outputresource:$@;2
# Rules for generating static lib
$(CFG)\$(PLAT)\harfbuzz.lib: $(harfbuzz_dll_OBJS)
@-if not exist $(CFG)\$(PLAT)\harfbuzz mkdir $(CFG)\$(PLAT)\harfbuzz
lib /NOLOGO /OUT:$@ $(harfbuzz_dll_OBJS)
$(CFG)\$(PLAT)\harfbuzz-icu.lib: $(harfbuzz_icu_OBJS)
@-if not exist $(CFG)\$(PLAT)\harfbuzz-icu mkdir $(CFG)\$(PLAT)\harfbuzz-icu
lib /NOLOGO /OUT:$@ $(harfbuzz_icu_OBJS)
$(CFG)\$(PLAT)\harfbuzz-gobject.lib: $(harfbuzz_gobject_OBJS)
@-if not exist $(CFG)\$(PLAT)\harfbuzz-gobject mkdir $(CFG)\$(PLAT)\harfbuzz-gobject
lib /NOLOGO /OUT:$@ $(harfbuzz_gobject_OBJS)
# Rules for linking Executables
- 使用管理员权限启动Visual Studio 命令提示(2010)并cd到F:\harfbuzz-1.2.0\src。
- 执行ragel命令从.rl文件生成.hh文件
ragel -e -F1 -o hb-buffer-deserialize-json.hh hb-buffer-deserialize-json.rl
ragel -e -F1 -o hb-buffer-deserialize-text.hh hb-buffer-deserialize-text.rl
ragel -e -F1 -o hb-ot-shape-complex-indic-machine.hh hb-ot-shape-complex-indic-machine.rl
ragel -e -F1 -o hb-ot-shape-complex-myanmar-machine.hh hb-ot-shape-complex-myanmar-machine.rl
ragel -e -F1 -o hb-ot-shape-complex-use-machine.hh hb-ot-shape-complex-use-machine.rl
- cd到F:\harfbuzz-1.2.0\win32,执行nmake命令分别生成release和debug版本的静态库文件。
nmake /f Makefile.vc CFG=release
nmake /f Makefile.vc CFG=debug
2.6 编译freetype 2.6.1
- 从官网下载freetype2.6.1,解压到一个本地路径(不妨假设解压到F:\freetype-2.6.1)。
- 使用vs2010启动F:\freetype-2.6.1\builds\windows\vc2010\freetype.sln。
- 使用Debug Multithreaded和Release Multithreaded配置分别编译生成库文件。
3. 数据库相关配置项的说明
| Qt 配置项 | 说明 | 主头文件 | 主库名 | 建议版本 (除特殊标记,均已验证XP可用) | 下载地址 |
|---|---|---|---|---|---|
| -mysql | MySQL / MariaDB 数据库驱动 | mysql.h | libmysql.dll | MySQL 5.1 | MySQL 5.1 归档 |
| -psql | PostgreSQL 数据库驱动 | libpq-fe.h | libpq.dll | PostgreSQL 9.3.25 | PostgreSQL 9.3.25源码 |
| -oci | Oracle 数据库驱动 (OCI) | oci.h | oci.dll | Oracle 10 客户端 | Oracle 10 客户端与SDK (需Oracle账号) |
| -odbc | ODBC 数据源驱动 (通用) | sql.h, sqlext.h | odbc32.dll (系统自带) | Windows XP 内置 ODBC | Windows 操作系统自带 |
| -tds | 连接 MS SQL / Sybase (需 FreeTDS) (已过时,不建议使用) | sybdb.h (FreeTDS提供) | NTWDBLIB.DLL | FreeTDS 0.91 (未验证) | FreeTDS 0.91 源码 |
| -db2 | IBM DB2 数据库驱动 | sqlcli1.h | db2cli.dll | IBM DB2 10.1.0.2 | IBM DB2 10.1.0.2 归档 (需IBM账号) |
| -sqlite | SQLite 3 数据库驱动 | sqlite3.h | sqlite3.dll | SQLite 3.8.x | SQLite 3.8.11 下载 |
| -sqlite2 | SQLite 2 数据库驱动 (已过时,不建议使用) | sqlite.h | sqlite.dll | SQLite 2.8.17 (未验证) | SQLite 2.8.17 源码 |
| -ibase | InterBase 数据库驱动 | ibase.h | gds32.dll | InterBase XE | InterBase XE 下载链接 |
4. 编译Qt5.6.3
4.1 configure的注意事项
- Qt5.6.3的configure未支持-recheck-all,而编译过程很大概率会有反复。为了避免反复nmake distclean(非常耗时),使用影子构建(Shadow Build)的方法。即每次构建都从一个全新的目录发起,保持源码目录干净。不妨为Qt源码文件夹的新建一个同级(影子)文件夹,例如qt563_202606200839,从此文件夹开始configure和nmake。如果有问题,只需要弃用影子文件夹即可。
- SQLite 2是一个非常古老的数据库,而SQLite 3 是对 SQLite 2 的一次重大且不兼容的升级。两者在数据库文件格式、核心 API 以及功能特性上均有根本性差异。考虑实际使用情况,SQLite 2目前极少被使用。为了简化编译工作,此次不编译sqlite2相关模块(包括源码和plugin)(no-sql-sqlite2)。
- tds(DB_Library)是一个非常古老的数据库通信协议,Microsoft官方早已停止支持,Sybase也宣布在16.0 SP04版本中不再支持。考虑实际使用情况,tds除了在一些上古项目中续存,目前极少被使用。为了简化编译工作,此次不编译tds相关模块(包括源码和plugin)(no-sql-tds)。
- 不使用-I、-L参数指定第三方库的头文件文件夹路径和库文件夹路径,避免在Qt环境中产生不利于迁移的硬编码。
综上,此次编译的configure指令如下:
..\qt-everywhere-opensource-src-5.6.3\configure -debug-and-release -static -static-runtime -mp -plugin-sql-mysql -plugin-sql-psql -plugin-sql-oci -plugin-sql-odbc -no-sql-tds -plugin-sql-db2 -plugin-sql-sqlite -no-sql-sqlite2 -plugin-sql-ibase -system-sqlite -opengl es2 -no-warnings-are-errors -system-zlib -system-pcre -system-libpng -system-libjpeg -system-freetype -system-harfbuzz -openssl -prefix ".\install" -nomake tests -nomake examples -opensource -confirm-license -platform win32-msvc2010
由于configure指令中没有额外指定-I和-L参数,也即没有指定第三方库/数据库驱动等外部头文件、库文件位置。我的想法是:
- 直接nmake,提示缺什么头文件,就从前面编译的源码包或者驱动包里面找,放入影子文件夹\qtbase\include中;
- 提示链接不到对象,就把前面编译好的/驱动包里的库文件重命名后,放入影子文件夹\qtbase\lib中。
这样能确保Qt编译完成,又不会硬编码额外的路径。以下是对这个曲折反复过程的一些记录。
4.2 无法解析的外部符号 _compress2,该符号在函数 "class QByteArray __cdecl qCompress(unsigned char const *,int,int)" (?qCompress@@YA?AVQByteArray@@PBEHH@Z) 中被引用
这是典型的导出符号问题,指向zlib库。回去看了下zlib的编译工程,发现有一个ZLIB_WINAPI被设置了。其作用如下代码所示:
// zconf.h
/* If building or using zlib with the WINAPI/WINAPIV calling convention,
* define ZLIB_WINAPI.
* Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
*/
# ifdef ZLIB_WINAPI
# ifdef FAR
# undef FAR
# endif
# include <windows.h>
/* No need for _export, use ZLIB.DEF instead. */
/* For complete Windows compatibility, use WINAPI, not __stdcall. */
# define ZEXPORT WINAPI
# ifdef WIN32
# define ZEXPORTVA WINAPIV
# else
# define ZEXPORTVA FAR CDECL
# endif
# endif
也就是说zlib库按照WINAPI的方式导出了,这在静态库中不是必要的。取消该定义,重新生成zlib库。重新nmake后问题解决。
4.3 error LNK2001: 无法解析的外部符号 _ScriptPlace@36等
链接libhargbuzzsl.lib时,报了这几个错,而报错信息指向hb-uniscribe.cc。
这几个外部符号包含在 Windows 的 usp10.lib库中。编辑harfbuzz-1.2.0\src\hb-uniscribe.cc文件,添加#pragma comment(lib, "usp10.lib"),而后重新编译harfbuzz。
// hb-uniscribe.cc
#define HB_SHAPER uniscribe
#include "hb-shaper-impl-private.hh"
#include <windows.h>
#include <usp10.h>
#pragma comment(lib, "usp10.lib") // 新增
#include <rpc.h>
#include "hb-uniscribe.h"
重新nmake,问题解决。
4.4 error LNK2001: 无法解析的外部符号 __imp__UuidCreate@4
链接libhargbuzzsl.lib时,报了这个错,而报错信息指向hb-uniscribe.cc。
这个外部符号包含在 Windows 的 rpcrt4.lib库。编辑harfbuzz-1.2.0\src\hb-uniscribe.cc文件,添加#pragma comment(lib, "rpcrt4.lib"),而后重新编译harfbuzz。
// hb-uniscribe.cc
#define HB_SHAPER uniscribe
#include "hb-shaper-impl-private.hh"
#include <windows.h>
#include <usp10.h>
#pragma comment(lib, "usp10.lib")
#include <rpc.h>
#pragma comment(lib, "rpcrt4.lib") // 新增
#include "hb-uniscribe.h"
替换文件后重新nmake,问题解决。
4.5 收尾工作
- 当所有编译工作完成(nmake全部跑完不再报错),执行nmake install。
- 将单独编译的库、数据库驱动库的lib文件拷贝到install\lib中。
- install\bin文件夹新建一个qt.conf文件,并写入内容如下:
[Paths]
Documentation=../../Docs/Qt-5.6.3
Examples=../../Examples/Qt-5.6.3
Prefix=..
5. 测试
我用新生成的Qt环境编译了一个需要用到ssl、数据库的小工程,带上依赖库后在windows XP虚拟机中运行正常。(其中IBM DB2依赖文件较多,使用安装包安装)
二零二六年六月二十日至二十五日 顾毅写于厦门
















