以QT为例谈环境搭建

以QT为例谈环境搭建

作者:哲思

时间:2022.1.5

邮箱:1464445232@qq.com

GitHub:zhe-si (哲思) (github.com)

前言

自从实习结束,好久没写博客了。作为2022年的开端,打算先谈一谈几乎让所有开发人员发愁的问题——环境搭建。而契机,是最近在615所做的一个QT项目中QT的环境搭建。

本文主要有两个目的:

  1. 提供一种简单、有效的QT安装与环境配置解决方案
  2. 从通用软件层面聊一聊对软件环境的理解,并尝试回答:我们配的环境,到底是什么?

注:本文提到的“软件”,不谈软件工程中 程序 + 数据 + 文档 的定义,而指一个完整可运行的程序。

QT安装与环境配置

QT简介

说起qt,很多人下意识的认为这是一个c++的界面开发库,qt确实在界面开发中使用最广泛,但它并不止于此。

简单来说,Qt 是一个跨平台C++应用程序开发框架,并提供给开发者一个便捷的图形界面开发工具与较为高级的面向对象开发方法。

大家安装与配置QT,只需按照以下步骤进行:

  1. 下载QT
  2. 安装QT
  3. 配置任意一个 qt 开发环境
  4. 配置 qt 运行时环境

QT下载

  • 途径一:官网下载,在 Try QT -> Download QT 注册后下载,当前QT官网下载都需要注册,在安装中也需要登录账号。

  • 途径二:百度网盘下载,笔者提供的是 qt 5.12.12 离线安装包(qt-opensource-windows-x86-5.12.12.exe)和 qt 在线安装包(qt-unified-windows-x86-4.2.0-online.exe)

    下载链接,提取码:lznc

    :可通过关闭互联网的方式跳过安装包的登录过程

  • 途径三:清华镜像下载

    镜像中 qt 5.14.2 (该版本在大部分场景已经足够高)及以下版本提供了安装包,以上版本只有源码,若想以安装包安装,可以使用官网的在线下载器下载安装。

QT安装

离线安装包

  1. 双击打开安装包,如图

    qt-ins-0

    左边是安装步骤,在 welcom 步骤需要登录QT官网的账号,此步骤可通过关闭网络后重新打开安装包跳过。

  2. 如果登录了,会增加一个 open source obligations 步骤让你确认开源协议,个人用户可忽略,勾选两个选项。

    qt-ins-1

  3. setup 步骤直接next;在 installation floder 步骤选择安装位置,勾选“Associate common file types with Qt Creator”会自动关联Qt相关的文件格式到Qt Creator(就是默认用Qt creator打开)

  4. Select Components 步骤选择了安装的具体组件,先从内容比较少的 Developer and Designer Tools 看起。

    qt-ins-2

    该部分是 Qt 的额外工具部分,不是 Qt 的库,包括了必选的 Qt Creator,和可选的 Qt Creator debug工具、MinGW 工具链套件、Perl语言构建工具。默认选择如图。

    MinGW 是 Qt 附赠的工具套件,构建工具链需要和 Qt 的版本兼容,若为了避免版本不兼容的麻烦,推荐勾选该构建工具并用它构建Qt项目。

    Qt + 版本号目录下的组件是 Qt 库中的内容,如下图:

    qt-ins-3

    在 Sources 之上的是Qt的各种版本,Qt版本对应构建工具链,需要与构建工具链版本兼容(一般会向下兼容,但尽量保持一致)。各种版本的 Qt 根据使用场景至少选择一套。

    • MSVC是visual studio(以下简称vs)的默认构建工具,版本对应vs的版本。举例:使用64位 vs2017的用户需要选择MSVC 2017 64-bit。

    • MinGW,是 GNU 针对 windows系统的最小头文件与库的集合,通过将 Linux 标准的API接口翻译为 Windows 标准的API接口来实现跨平台项目开发,此处笔者推荐采用 MinGW 开发(更标准,易跨平台)。举例:开发工具链选择MinGW7.3.0-64bit,则此处选择

    • UWP是 windows 提出的通用 windows 开发的项目,期望让不同种类的终端上跑的windows系统提供相同的API。

    • 同时,Qt还为 Android 提供了一套开发工具库。

    Sources是Qt的源代码,推荐勾选;其下是Qt的补充库,提供了web等相关组件,并不必要,但由于不大,可以勾选上。

  5. 之后就一路next就好,如果让同意协议,就点击同意,其他全部默认,安装成功撒花。

在线安装包

在线安装包是一个下载器,可以根据你的选择自动从官方仓库下载对应组件,版本可以自由挑选,但可能下载较慢。

安装的基本步骤与离线一致,在选择安装组件的时候,在线安装包显示了当前所有版本的 qt,以下是选择 qt6.1.2 时笔者推荐的选择,如有特殊需要(如串口相关),可以在Additional Libraries中添加,这里笔者在 Tools 中选择了与 Qt6.2.1 中 QT MinGW版本一致的 MinGW工具链套件,如下图:

qt-ins-4

qt-ins-5

QT开发环境配置

QT Creator配置

无需配置,在安装时自动配好,QT Creator会自动找到所有安装的QT 版本、构建工具链(此处为编译器)、debugger、cmake。当前配置可在 工具 -> 选择 -> Kits 查看与修改。

qt-creator-1

MinGW 构建工具链直接包含了debug工具 gdb.exe,可以直接使用。

若使用 msvc 构建工具链,其中不包含 debug 工具,需要自行补充安装。Windows 10 的调试工具(WinDbg)集成在了 Windows 10 sdk中,若没有安装过该 sdk,可以根据官网教程安装或者安装一个vs;若已经安装过 Windows 10 sdk(安装过vs一般会默认安装一个),可以在 控制面板 -> 程序和功能 找到 Windows Software Development Kit- Windows,右键更改,添加debug工具,如下:

qt-msvc-dbg-1

qt-msvc-dbg-2

qt-msvc-dbg-3

配置其他开发环境的 msvc debug工具,方法同上。

visual studio配置

  1. 在 vs 工具 -> 拓展与更新 -> 联机 搜索与安装 Qt Visual studio Tools,之后重启vs

    qt-vs-1

  2. 在 vs -> Qt VS Tools -> Qt Versions 配置对应版本的QT目录,需要在 path 选择 qt 安装路径下 bin -> qmake.exe,在 vs 中要配置 msvc 版本的 qt。(该界面可能在不同 vs 版本下不同,但都是添加新安装的 qt 版本)

    qt-vs-2

  3. 配置项目的 qt 参数,在项目右键 -> 属性 -> Qt Project Setting

    qt-vs-3

    主要参数有两个,在 Qt Installation 选择刚配置的 qt,在 Qt Modules 添加需要使用的 Qt 模块。

clion配置(CMake)

clion无自己的工程配置,而是采用了 Cmake 进行项目管理。笔者比较喜欢使用该方式,配置比较清晰,项目也比较跨平台。

clion需要配置两个地方,构建工具链与工程的 CmakeLists 文件。

  1. 构建工具链

    clion没有默认的构建工具链,需要自行安装 MinGW、clang或使用 vs 的构建工具,若在环境变量配置了构建工具,可自动找到。也可以自行配置,在 文件 -> 设置 -> 构建、执行、部署 -> 工具链 点击 + 进行添加。

    在最上面的为默认构建工具链,在项目中也可以指定其他的,但要选择与 qt 版本兼容的构建工具链(最好版本对应)。

    qt-clion-1

  2. CmakeLists.txt

    CmakeLists.txt 是 cmake 工程的工程文件,描述了工程的所有配置与构建,具体语法推荐大家在官网教程中学习。

    以下给出一个 cmake 配置 qt 的 CmakeLists 配置,相信大家都一目了然:

    cmake_minimum_required(VERSION 3.15.5)
    project(ProjectName)
    
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTORCC ON)
    set(CMAKE_AUTOUIC ON)
    
    
    # 本地qt安装路径(指定了库的查找位置,不是运行环境,若不指定,从path读取,若找不到,找不到Qt库)
    set(CMAKE_PREFIX_PATH "D:/env/c++/qt/qt5_12_12/5.12.12/mingw73_64")
    
    # 从 qt 库中找到对应的组件库(需要根据自己需要设置),但还没将库链接到产物
    find_package(Qt5 COMPONENTS
            Core
            Gui
            Widgets
            SerialPort
            Xml
            REQUIRED) # 表示如果找不到某个组件,cmake加载项目时报错
    
    # 指定头文件的搜索路径
    include_directories(
    	# find_package core组件自动添加qt include目录
    )
    
    # 指定引用的外部库的搜索路径
    link_directories(
    	# find_package 导入组件时,就已经找到组件的位置,无需额外指定
    )
    
    # 添加产物
    add_executable(EXEName
            main.cpp
            )
    # 链接lib到产物(将 Qt 的库也链接进去)
    target_link_libraries(TE_TEST
            Qt5::Core
            Qt5::Gui
            Qt5::Widgets
            Qt5::SerialPort
            Qt5::Xml
            )
    

QT运行环境配置

将安装的 qt 下的 bin 目录配到环境变量 path 中即可。

构建工具链(MinGW、MSVC等)无需配置到 path 环境变量中,但如果想要在命令行使用 gcc 命令等,可以进行配置。注意不要把多个版本的构建工具链在 path 中进行配置。

小结

至此,QT环境配置完成,从在集成开发环境编写代码并编译、编译过程正确连接到对应的静态链接库,到在 ide 中运行/调试或直接双击 exe 打开。

软件环境理解

作为一个软件开发者,笔者多次被配置环境所折磨,尤其是跑别人的项目或者使用框架的时候。

这次配置 QT 环境,原来的项目是在 vs 下的 QT工程,经笔者根据网上教程的小心配置,终于不再飘红,跑过了构建,又在运行过程中找不到 libgcc_s_seh-1.dll和QtCored.dll、无法定位到程序输入点于动态链接库等诸多问题,如下:

qt-error-5 qt-error-3 qt-error-6 qt-error-4 qt-error qt-error-2

经过艰苦卓绝的奋战,终于解决了一个个奇怪的问题,并搞清了问题的根源。忽然回顾了一下多次配环境的经历与解决方法,发现原理都大同小异,在搞明白了之后,配其他环境也有种轻车熟路的感觉。所以想借此机会谈一谈,什么是软件环境?到底如何配软件环境?

QT环境

重新回顾一下我们刚刚配的 QT 环境,我们都干了什么?

安装QT

首先我们下载安装了一个QT,安装是什么?

最简单的安装就是解压压缩包。安装包就是一个压缩包,内部可能包含了多个可执行文件(exe)、动态链接库(dll)等文件,我们将它解压缩到某个文件夹就是最简单的安装过程。而我们就是要正确运行其中的某个或某几个可执行文件。

有时,安装中还伴随着下载过程,相当于安装包只包含了部分的程序文件,在安装中可以根据用户选择自动下载其他附加功能的程序文件。

在安装中,有时要我们选择是否在桌面或者菜单添加快捷方式,其实就是根据程序的主可执行文件生成快捷方式并放到桌面或者菜单文件夹。以下是笔者的菜单文件夹:

env-1

有些时候,程序希望能在全局找到自己,就会把自己的可执行文件或者动态链接库添加到 path 环境变量下,或者放到已经添加到 path 的文件夹中。可以在cmd输入“path”查看当前的path环境变量包含的内容:

env-2

还有些时候,安装包希望让操作系统感知到安装的软件,还会把自己的信息写到注册表中。下面是笔者电脑中QQ在注册表中注册的信息:

env-3

安装还有根据软件需要而产生的很多个性化需求,但无外乎都是让安装的软件读写某些信息或者找到某个文件,进而可以正确运行。

对于 QT,它从安装包与网络上的官方仓库获得需要的文件后,全部放到了安装路径,并将自己的信息写到注册表,告诉了操作系统它的存在。但并未在环境变量进行配置,所以无法在全局直接找到 QT。

配置QT开发环境

接下来,我们配置了 QT 的开发环境。

像开发c/c++的 dev c++、visual studio、clion、开发 java 的 eclipse、idea等,相信大家都不陌生。而他们都有一个统一的名字,叫 IDE,全名“集成开发环境”。下图是笔者常用的 IDE :

env-ide-1

引 RedHat 对 IDE 的定义:集成开发环境(IDE)是一种用于构建应用程序的软件,可将常用的开发人员工具合并到单个图形用户界面(GUI)中。

IDE 主要包括一个源代码编辑器、本地构建版本自动化工具(也就是代码的编译、封装等)、调试器,当然还可能集成了一些其他开发中的工具,如数据库访问工具、性能分析器等。

所以顾名思义,我们配置 QT 的开发环境,就是将 QT 的开发 sdk 集成到 ide 中(让 IDE 能找到 QT 的 sdk),好让 ide 可以使用 sdk 完成 QT 的配置、构建、运行/调试工作,同时复用 ide 提供的其他工具。

在前面,我们配置了 c++ 的构建工具链(MinGW、MSVC),用来构建一般的 c++ 程序。

同时配置了 QT 的特有构建工具

  • vs 的 QT VS Tools 采用的是 qmake 进行项目构建描述,需要配置 qmake 的位置,而 qmake 知道如何找到和使用 QT 对应的构建工具与库。
  • clion(cmake)不直接配置并使用 QT 的某个工具,而是采用 cmake 对构建的通用描述能力。QT 在库中编写 cmake 可以直接读取的库的描述文件。该描述文件描述了应该如何找到 uic、rcc、moc 等 QT 特有的构建工具并使用它们将 .ui、.rcc、QT 元对象等处理为一般 c++ 的源代码。该描述也说明了 QT 库的位置,所以我们才可以通过 find_package 自动找到对应的库。

配置QT运行时环境

最后,我们配置了 QT 的运行时环境。

运行时环境是什么?顾名思义,就是程序运行的时候它所处的环境。而我们说某个软件的运行时环境,也就是在说该软件对外界的依赖。

这样就很清晰了,为什么软件在运行时环境不对时会报错?因为软件在这个运行时环境中运行,依赖(也就是使用)了环境中的其他东西,当这个东西找不到或者和我们想象中的不一样,我们没法使用它时,程序就运行不下去了,就会报错。

那环境中我们的依赖是什么呢?在大部分情况下,我们可以说是动态链接库。动态链接库在 Windows 下后缀名为 .dll,在 linux 下为 .so。

那 QT 程序需要什么依赖呢?就是 QT 安装目录中 bin 内的 dll(Windows 下的动态链接库)。所以我们把 QT 的 bin 目录配到环境变量里即可。

注:此处无需配置构建工具链的 bin 目录。

软件环境

刚才我们重新回顾了一遍 QT 的安装与环境配置的流程与每一个流程的意义。回顾其他安装与配置的流程,便会发现都大同小异,而其中的原理都是一致的,我们可以基于 QT 的配置由点到面的总结一下:

一个程序,从源代码到运行,包括:编译(compile)、链接(link)、加载(load)、运行(execute),对应的GNU工具一般为:编译器compiler(gcc)、链接器linker(ld)、加载器loader。

一般编译和链接统称为编译,期间为编译时(compile time);而加载和运行统称为运行,期间为运行时或执行时(runtime/execution time)。

  • 安装

    安装就是解压,并帮助用户便捷的配置运行时环境的过程。概念比较简单,在上一节“QT 环境”的“安装QT”小节中已基本叙述清楚。

  • 软件环境

    • 开发环境(编译时环境是开发环境的子集):

      • 构建工具:如编译器、链接器、QT元对象处理器 moc、QT 用户界面编译器 uic、QT 资源文件编译器 rcc 等。这些构建工具也有自己的运行时环境(依赖其他动态库等),共同组成了被开发的软件的编译时环境。

      • 调试工具:gdb

      • 项目依赖的静态链接库、头文件与隐式加载动态链接库:

        项目在编译过程中需要使用的外部库。

        • 静态链接库:

          编译时(compile time)被使用(链接的时候)。在链接静态库的时候,链接器会在其中找到所需要链接的函数,然后将它们拷贝到执行文件,这种拷贝是完整的拷贝,所以在链接成功后,程序运行不需要静态库的参与。

        • 头文件:

          函数或类型等的声明文件,向编译器声明该函数或类型等已经存在,在其他地方有声明的具体实现,在链接的时候将二者关联起来,如果找不到具体实现,会报错:

          undefined reference to xxx
          

          静态链接库与隐式加载的动态链接库都是声明的实现可能存在的地方。

        • 隐式加载的动态链接库:

          在运行时环境介绍动态链接库时进行详细介绍。

      • 其他:如代码分析器、性能分析器等,文本编辑与代码提示也包含在内。

    • 运行时环境 / 执行时环境:

      • 动态链接库

        动态链接库的查找方式是名字查找,也就是说,调用某个函数,只要动态链接库内存在该名字的函数(函数签名也一致),哪怕不是一开始的动态链接库也可以正常调用。

        • 隐式加载

          编译时和运行时被使用。在编译时,链接器在其中找到所需要的函数(或其他对象文件),生成地址/位置无关代码(Position Independent Code (PIC)),并没有真正的实现拷贝;在运行时(runtime/execution-time),某个程序在运行中要调用某个动态链接库函数的时候,操作系统首先会查看所有正在运行的程序,看在内存里是否已有此库函数的拷贝了,如果有,则让其共享那一个拷贝;只有没有才链接载入。

          如果链接库被更换,系统会自动同步内存。

        • 显示加载

          在代码中主动调用系统 API 加载动态链接库。由于指定了准确的位置,与读取文件类似。

      • 其他软件

      • 数据库

      • 文件

      • 系统注册表

      • 等等,可以说凡是软件使用到的都是其运行环境,软件最大的软件环境就是操作系统了。

“配环境”,就是指让系统无二义性的找到我们想要的外部依赖。

开发环境

开发环境简单来说就是程序猿敲代码的环境,从我们创建一个项目、到源代码编辑与版本管理、再到源代码构建、最后到程序调试等一整个开发流程。

最基本的开发环境就是编译器和链接器,其次是文本编辑器,再然后是调试器、项目构建工具等等。

在开发中,我们可以分别使用每一个工具(可执行文件)去处理某个开发流程;也可以使用一个 ide 集成开发环境,告诉 ide 那些工具在哪里,这样你就可以在 ide 中便捷的使用所有的工具了。

一般开发环境问题较少,但请注意:

  • 有些库是与构建工具链的版本一一对应的
  • 有些库分 release / debug 版本、64 / 32 位,尽量选择场景对应的版本

运行时环境

运行时环境是出问题最多的地方,尤其是运行时环境中的动态链接库,因为它采用运行时动态查找链接库的位置。

我们还没有详细说明“找不到”和“不一样”的具体含义。那什么时候我们会找不到依赖呢?这就要先说系统是如何找这个依赖的,这里以 Windows 传统的桌面应用程序 和 linux 为例。

  • Windows 桌面应用程序

    在 Windows 传统的桌面应用程序中(官方说明),系统会按照以下顺序搜索目标(以下为默认的安全 DLL 搜索模式,非安全模式就是将第5项提前插入到第二项,也就是当前目录优先于系统的库目录):

    1. 从其中加载应用程序的目录。
    2. 系统目录。 使用 GetSystemDirectory 函数获取此目录的路径。
    3. 16 位系统目录。 没有函数可获取此目录的路径,但会进行搜索。
    4. Windows 目录。 使用 GetWindowsDirectory 函数获取此目录的路径。
    5. 当前目录。
    6. PATH 环境变量中列出的目录。 请注意,这不包括应用路径注册表项指定的 每个应用程序路径 。 计算 DLL 搜索路径时,不会使用应用路径密钥。
  • linux

    1. 编译目标代码时指定的动态库搜索路径;
    2. 环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径;
    3. ldconfig 缓存配置文件 /etc/ld.so.conf 中指定的动态库搜索路径;
    4. 系统默认动态库搜索路径 /lib;
    5. 系统默认动态库搜索路径 /usr/lib。

找不到动态链接库,通常会报错:

由于找不到 dll, 无法继续执行代码。

这里还有几个注意点:

  • 查找的顺序的前后

    系统会按照顺序根据库的名字进行查找,直到找到第一个符合的动态库。也就是说当出现同名动态库,前面的配置优先于后面的配置。

    这里还要注意,一般有一个环境变量作为用户自定义的动态库搜索路径集合,而该环境变量的赋值是覆盖方式,可能存在非增量式覆盖或覆盖了后面其他程序使用的动态链接库。

  • 内存已经加载某静态库的问题

    如果内存已加载某动态库,则后来的程序如果依赖该名字的动态库,系统会直接使用内存中的同名动态库,不会重新搜索。

而“不一样”指的是与预期调用的函数声明不一致。这可能由于版本不一致,也可能被后添加的同名库覆盖导致调用了错误的动态库等,而该错误下通常会报错:

无法定位程序输入点 xxx 于 xxx.dll 上

总结,把书本读薄

读到这里,想必大家对软件环境与环境搭建有了一定的认识,但可能上面的文字太多,还不够清晰,让我们一起把书读薄。

  • 环境主要有两种:开发环境、运行时环境
  • 开发环境下的产物就是软件本身,而软件想要独立运行,就需要在合适的运行时环境下
  • 配置环境就是让系统无二义性的找到外部的依赖,如构建工具、静态库、动态库等
  • 配置开发环境重点要解决构建工具链与静态链接库的兼容、静态链接库与构建目标(x86 / x64、release / debug)的兼容
  • 配置运行时环境重点解决确定软件的实际运行时环境、让系统正确找到对应的动态链接库两个问题

后记

理解了软件的环境与环境的搭建对一个开发人员十分重要,不但大大减轻了配环境带来的成本,也让我们的软件包更清晰易用、兼容性好。

包括 python 的 pip、乌班图的 apt 包管理器也是这个道理,但它们通过更清晰的软件包的依赖自描述让软件包管理变得更加明确、简单。而很多游戏将所有依赖的动态链接库都打包到游戏中也是一个经典的解决运行时依赖的方法。

最后,献出美好的祝福:愿世间再无因配环境而痛苦的程序猿!

posted @ 2022-03-13 17:37  _哲思  阅读(2078)  评论(2编辑  收藏  举报