用静态链接Qt库的方式使用Qt(上)

文中涉及的静态链接Qt的方法,都是仅限于在Windows下的情况。

首先说点废话套话。用Qt框架写GUI程序,要做出来能在其它电脑上运行的软件包是一件麻烦的事情。编译生成出来的程序通常情况下要带一大堆Qt的DLL文件,还需要用特定的目录结构组织起来。Qt虽然提供了windeployqt.exe可以一条命令就能把Qt应用程序依赖的Qt库复制到程序所在的目录,能保证程序正常运行,但是对一些简单的项目来说,程序目录下带了一堆DLL文件和qm文件一点也不简洁,编译出来的程序只有单个可执行文件,只复制一个EXE就是要比EXE附带一大堆DLL要简单。要让使用Qt的程序不依赖Qt的DLL就能运行,就得想办法把Qt库静态链接到程序中与程序合为一体。

软件许可协议问题

虽然代码的许可协议在天朝几乎等同于废纸,但是还是强调一下静态链接带来的法律风险。

Qt提供了两种许可方式:商业许可证或者LGPL许可证。在LGPL协议的情景下引用Qt的代码,动态链接Qt库时,自己的项目是闭源的没有任何的问题,只是修改了Qt代码时要把相应的修改也开源出来;静态链接Qt库时,避免违反协议要做的事情就麻烦的多,按照GNU FAQ(https://www.gnu.org/licenses/gpl-faq.en.html#LGPLStaticVsDynamic)的说法,闭源软件可以静态链接LGPL协议的库,但是必须提供途径使得用户可以方便地把LGPL库的修改链接到闭源的代码上,重组成新的程序;实际操作上就是要把调用LGPL库的代码都公开,并且公开代码生成出来的所有.obj文件或者.o文件,还要提供链接生成新程序的链接命令;这套操作实践起来是如此的困难以至于从省事的角度上不如直接将源码公开。必须有静态链接的需求,开发的还是闭源软件,又不差钱的情况下,最省事的方式就是向The Qt Company买Qt许可证为Qt的发展做贡献了,Qt的商业许可证完全没有LGPL这些条条框框的限制。

因此要静态链接Qt库到自己的项目时,请务必考虑这样做会带来的法律风险。

编译静态库形式的Qt库

Qt提供的安装包默认是没有静态库形式的Qt库的,编译成静态库的Qt库需要自己来编译。事实上动态库形式的Qt库确实也能满足绝大部分的需求,但咱们现在必须要静态库形式的Qt库,就是看Qt5Core.dll这堆DLL不顺眼。所以接着往下写。

我这里编译的Qt库的版本是5.9.8,在5.12版成为LTS版本之前,是最新的LTS版本。

获取Qt的源代码

虽然通过Qt的安装程序就自动安装Qt的源代码到电脑上,但是要考虑到Qt是一个巨大的项目,编译生成出来的中间文件大小也是非常可观不可小视的,我这里没有编译QtWebEngine,编译成功后源代码的目录大小就超过了10GB,之后install出来的Qt静态库所在目录的大小也有将近5GB,要编译QtWebEngine就涉及到了Chromium内核,占用的空间只会更大。而Qt安装程序会默认把Qt安装到操作系统所在的分区,由于磁盘空间不够编译失败是一件很尴尬的事情,所以建议把源代码复制到磁盘空间够的地方来编译,或者单独下载源代码然后解压到空间够的地方。

这里先下载Qt的源代码压缩包,然后解压:
https://download.qt.io/official_releases/qt/5.9/5.9.8/single/qt-everywhere-opensource-src-5.9.8.zip

编译的环境要求

Qt的这篇文档指出了Windows环境编译Qt库所需的工具:https://doc.qt.io/qt-5.9/windows-requirements.html

编译过程依赖的工具有:
– ActivePerl: 不太推荐5.28和以后的版本,5.28版废弃了一些Perl模块会导致编译不成功,编译OpenSSL 1.0.2的时候用了5.28版,在configure时会出错。
– Python: 用的是Python3,编译QtWebEngine时编译脚本不支持Python3,不编译QtWebEngine我这里用Python3是没有问题的,不过2020年Python2就停止维护了,Chromium的depot_tools工具集里面依赖的Python2什么时候能换掉啊!
– GPerf、Bison、Flex:Qt依赖的ANGLE库编译过程中使用了gnuwin32项目里的这些程序。为了编译的方便,Qt的源码包里默认提供了这些程序,在gnuwin32/bin目录下。
– OpenSSL: Qt使用OpenSSL提供对TLS的支持,Qt5.9只支持OpenSSL 1.0.2版,但是Qt5.12支持OpenSSL 1.1和更高的版本。

编译前请务必保证所有所需的工具加入到了PATH环境变量中。

Qt5.9在Windows下支持用MinGW/Clang/MSVC(2013/2015/2017)编译器编译,这里我用了vc2017的x86工具集编译Qt库。

Configure和编译

Qt的文档给出了在Windows下从源码构建的具体步骤:https://doc.qt.io/qt-5.9/windows-building.html

  • 先打开VC命令提示符,因为我要用vc2017工具集生成x86版的Qt静态库,所以我在开始菜单里选Visual Studio 2017/x86 Native Tools Command Prompt for VS 2017这个快捷方式打开命令提示符,这个快捷方式会运行vcvarsall.bat帮你把msvc对应的工具集配置好。

  • 把编译依赖的工具所在的路径加入到PATH环境变量,假设源码被解压到了C:\qt5-src

    SET PATH=C:\qt5-src\qtbase\bin;C:\qt5-src\gnuwin32\bin;%PATH%
    
  • 切换到源码目录,用下面的命令来配置编译所需的工具集:
    configure -static -skip webengine -opensource -debug-and-release -no-pch -nomake tests -nomake examples -force-debug-info -platform win32-msvc -opengl desktop -prefix F:/Qt-5.9-static-msvc2017-x86
    
    • 解释下configure命令的参数:
    • -static: 把Qt库编译成静态库。
    • -skip webengine: 不要编译QtWebEngine。文中也没有涉及这个模块的编译。
    • -opensource: 向configure脚本声明我们要按LGPL协议的要求使用Qt库。
    • -debug-and-release: 编译生成debug版和release版两份Qt库(windows only)。
    • -no-pch: 编译过程中不使用预编译头。
    • -nomake tests: 不要编译Qt的测试用例。
    • -nomake examples: 不要编译Qt带的示例程序。
    • -static: 把Qt库编译成静态库。
    • -force-debug-info: 为Release版的Qt库生成调试符号(编译成静态库大概不需要,去掉应该也可以)。
    • -platform win32-msvc: 用MSVC编译器编译Qt库。
    • -opengl desktop: 使用操作系统提供的OpenGL API。
    • -prefix F:/Qt-5.9-static-msvc2017-x86: 编译成功然后运行install命令时把Qt库安装到F:/Qt-5.9-static-msvc2017-x86这个目录。
    • 要添加TLS的支持,需要在configure脚本里指定预先编译好的OpenSSL所在的目录。如何编译OpenSSL1.0.2版可以参照这篇博客:http://p-nand-q.com/programming/windows/building_openssl_with_visual_studio_2013.html
    • 添加参数的方法是configure参数里加上这些:
      -ssl -openssl-runtime -I "<path-to-openssl-1.0.2>/include"

      • 参数的含义是:生成出的二进制文件要支持TLS;使用动态库形式的OpenSSL库,程序运行时会动态加载OpenSSL的DLL;把OpenSSL头文件所在的目录加入到configure的环境变量中。
    • configure脚本可以加的参数不止这些,输入configure --help 能看到所有参数对应的帮助。
  • 输入configure命令后脚本会提示你是否同意LGPL协议,输入y同意(再次请注意法律风险!):
    Microsoft (R) Program Maintenance Utility Version 14.16.27034.0
    Copyright (C) Microsoft Corporation.  All rights reserved.
    
    
    This is the Qt Open Source Edition.
    
    You are licensed to use this software under the terms of
    the GNU Lesser General Public License (LGPL) version 3
    or the GNU General Public License (GPL) version 2.
    
    Type 'L' to view the GNU Lesser General Public License version 3 (LGPLv3).
    Type 'G' to view the GNU General Public License version 2 (GPLv2).
    Type 'y' to accept this license offer.
    Type 'n' to decline this license offer.
    
    Do you accept the terms of either license? 
    
  • configure完成时脚本会输出一份编译Qt设定选项的摘要,可以核对下是否符合自己的想法:
Configure summary:

Build type: win32-msvc (i386, CPU features: sse sse2)
Configuration: sse2 sse3 ssse3 sse4_1 sse4_2 avx avx2 compile_examples f16c force_debug_info largefile debug_and_release release debug build_all c++11 concurrent dbus no-pkg-config release_tools static stl
Build options:
  Mode ................................... debug and release (with debug info); default link: debug; optimized tools
  Optimize release build for size ........ no
  Building shared libraries .............. no
..........................
Qt Multimedia:
  ALSA ................................... no
  GStreamer 1.0 .......................... no
  GStreamer 0.10 ......................... no
  Video for Linux ........................ no
  OpenAL ................................. no
  PulseAudio ............................. no
  Resource Policy (libresourceqt5) ....... no
  Windows Audio Services ................. yes
  DirectShow ............................. yes
  Windows Media Foundation ............... yes
  Media player backend ................... DirectShow

Note: Using static linking will disable the use of dynamically
loaded plugins. Make sure to import all needed static plugins,
or compile needed modules into the library.

Note: No wayland-egl support detected. Cross-toolkit compatibility disabled.

Qt is now configured for building. Just run 'nmake'.
Once everything is built, you must run 'nmake install'.
Qt will be installed into 'F:\Qt-5.9-static-msvc2017-x86'.

Prior to reconfiguration, make sure you remove any leftovers from
the previous build.
  • 之后就是编译流程,输入命令:
nmake

或者可以用Qt文档介绍的jom工具让CPU核心得到充分利用,输入命令:

jom -j <core count>

Qt是个庞大的项目,编译时间当然也会非常漫长。电脑的CPU是八核十六线程的锐龙,编译大概花了半个多小时……

  • 编译成功后,输入命令nmake install把生成出来的Qt静态库安装到configure脚本指定的目录。
    用jom工具时,命令改成jom install

编译这就完成了。在安装目录的lib子目录下就能看到生成出来的Qt静态库,Debug版的和Release版的都有。

Qt代码量虽然庞大,但是整个编译过程并没有脱离configure -> make -> make install这个套路。难点还是指定好正确的configure参数和安装好所需的依赖,只要按照文档配置好是没有什么阻碍的。

其实真正难受的地方是在使用Qt静态库上面,Qt文档只说了如何在qmake项目里使用静态库的Qt,Qt提供的CMake脚本并没有考虑到静态库的情况,这个之后再介绍踩的坑。

参考资料

感谢一些博客和Qt社区帖子对这篇文章和我想办法编译静态Qt库提供的帮助:

下篇 用静态链接Qt库的方式使用Qt(下)

Leave a Reply

Your email address will not be published. Required fields are marked *