Day 02 项目启动

项目视图

按照分层设计原则划定软件的模块,分成四个模块:

1
2
graph TB
CellScanner --> Frameworks --> Adaptors

其中,

  • CellScanner:应用程序的界面部分。
  • Frameworks:程序逻辑部分
  • Adaptors:和外部设备的接口

其中,CellScanner是一个应用程序,而另外两个是DLL。

Qt提供了两种界面开发路径:基于传统C++的Widgets和新的QML。在本书中,因为各种原因,我们将技术栈设定为纯C++/Widgets的开发,但是为了以后或许有机会切换到QML(这并不是一个好主意,甚至可能根本不可行,我认为),我们明确划分前后台的职责,将界面无关的逻辑放到framework项目中,将外部接口相关的内容放到adaptor里面去(扫描仪控制,AI功能接口以及网络接口)。

项目结构如下:

1
2
3
graph TB
ScanSuit --> Adaptors & Frameworks & CellScanner & UnitTest
UnitTest --> 模块1 & 模块2 & ...

创建项目骨架

打开Qt,选择新建项目,选择其他项目/子目录项目。当询问是否添加子项目时,选择取消。这时就创建了一个新的空白的子目录项目。

我们也可以直接新建一个目录,并在目录中创建一个文本文件,编写其内容如下:

1
TEMPLATE = subdirs

保存文件并将文件后缀改名为.pro。这样也创建了新的空项目。

这是一个空白项目,我们接下来需要向里面添加其他的子项目。

添加Adaptor项目

在Qt Creator中打开项目,在父项目ScanerSuit上右键鼠标,选择新子项目

在项目类型中选择库/C++ Libiary,并进入下一步

选择项目类型为“Shared Library”,将Qt modulesCore改为Gui

以后我们也可以修改pro文件来修改包含的Qt 模块。我们使用gui是因为我们预计会用到Qt的QImage等数据类型。

语言栏中,选择Chinese(China)。注意,我们要注意,当确定软件一定需要包含多语种的时候(这个几乎是必然的),最好将源码设为英文,而将中文通过翻译得到——其他各种各样稀奇古怪的问题抛去不看,你至少应该考虑要是哪一天你的翻译文件坏了不得不删除时,你是对着英文翻译中文方便,还是对着中文翻译一堆英语名词方便——另外一个很明显的问题是,英文在绝大数时候对界面布局的浪费要远大于中文,如果界面是中文,很可能你辛苦调整好的界面换成英文之后一塌糊涂。相反,平时你使用英文,以后切换成中文的时候至少不会说少了一块。

最后确认一下结果,并按下完成按钮。

创建Frameworks项目

按照同样的方式,再添加一个项目Frameworks

创建Scanner项目

接下来我们创建CellScanner项目,这次它的项目类型是Widget Application。注意将主窗口类名字从缺省的MainWindow改为自己的名字ScannerMainWindow。其他的都不需要修改。

修改主窗口的缺省名称是一个好习惯,尤其是当你的项目中包含多个GUI子项目的时候。不然,到处都是MainWindowDialogWidget,最终你会不得不手工修改掉。

创建Test根目录

最后,我们创建一个单元测试项目的根目录,用于存放所有的单元测试项目。

Qt可以支持多种单元测试工具,在本项目中我们就使用它自带的QTEST就可以了。虽然它的功能比较简单,但是也够用了。

QTEST的主要的问题是要一个测试类一个工程,这样做有好处有坏处,在开发阶段,这个限制最大的问题是如果不加组织,会让工程中到处都是测试项目。为此,我们再通过一个子目录项目UnitTest来管理它,将所有的单元测试项目都放进去。

排除构建目录

另外注意一点的是,Qt Creator的不同版本定义的缺省构建目录是不一样的。在Qt6.7之前,构建目录在项目目录外面的同级目录,而Qt6.7开始,构建目录是在项目目录里面的build目录下面。因此,一定要注意两点:一是一定不要再在项目根目录下自己建立和使用build目录,二是,一定要把build加到配置管理软件的排除列表中。例如,对于git,我们要在.gitignore里面加入:

1
build/

设定项目依赖关系

接下来我们需要设置项目的依赖。首先为Frameworks项目添加依赖库Adaptors

在Creator中选择项目Frameworks,右键鼠标并选择菜单添加库...

选择内部库

在下拉框中选择Adaptors,并在支持平台中去掉LinuxMac,并确认库选择方式是debug或release子目录下的库,如下:

打开Frameworks.pro文件,我们看到增加了下面的内容:

1
2
3
4
5
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../Adaptors/release/ -lAdaptors
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../Adaptors/debug/ -lAdaptors

INCLUDEPATH += $$PWD/../Adaptors
DEPENDPATH += $$PWD/../Adaptors

然后按照同样的方式,将AdaptorsFrameworks都添加到CellScanner里面。

调整编译顺序和依赖关系

按照我们的方式,选择编译,大概率会编译失败:

1
2
LNK1104: 无法打开文件“Adaptors.lib”
LNK1104: 无法打开文件“Frameworks.lib”

Qt可以在pro文件中定义项目的依赖关系,但是这个大概率还会失败。我们还是最好手工调整编译顺序,让底层的项目先被编译,顶层的项目后被编译。qmake可以通过CONFIG = ordered命令来强制按照SUBDIRS里面的顺序来编译。

然后,我们还是需要在pro文件中定义项目的依赖关系,这样当build的时候才能更新。

1
2
3
4
5
6
7
8
9
10
11
TEMPLATE = subdirs
CONFIG = ordered
SUBDIRS += \
Adaptors \
Frameworks \
CellScanner \
UnitTest

Frameworks.depends = Adaptors
CellScanner.depends = Adaptors Frameworks
UnitTest.depends = Adaptors Frameworks CellScanner

然后,我们可以删除build目录下的内容,再试一下构建命令,这次能够编译成功了。

添加第三方库

我们的程序会用到一些第三方的库。首先就是OpenCV,另外还有支持zip的Quazip。因为它们被多个项目使用,为了以后修改的时候会到处修改,我们将它们定义到一个独立的项目文件中使用。

在项目根目录中新建一个文件thirdpart.pri。向里面添加OpenCV4.9.0的内容。其中,OPENCV490是我们定义的环境变量,它指向OpenCV4.9.0的安装目录。

1
2
3
4
win32:CONFIG(release, debug|release): LIBS += -L$$(OPENCV490)/x64/vc16/lib/ -lopencv_world490
else:win32:CONFIG(debug, debug|release): LIBS += -L$$(OPENCV490)/x64/vc16/lib/ -lopencv_world490d
INCLUDEPATH += $$(OPENCV490)/include
DEPENDPATH += $$(OPENCV490)/include

然后,在各个项目的pro文件中添加下面的一行:

1
include(../thirdpart.pri)

过一会儿,等QtCreator重新解析了pro文件后,就会在项目树中显示出来:

使用预编译头文件

在C++20的Module被广泛使用之前,大型C++项目的编译速度会始终是一个令人头痛的问题。预编译头文件是解决这个问题的一种手段。比如,在Visual C++中,stdafx.h是固定的预编译头文件。在Qt的应用程序开发中默认是没有预编译头文件的。我个人对Qt使用预编译头文件到底是加快编译速度还是减慢了速度一直搞不清楚,但是我还是比较喜欢使用它,我可以把一些常用的头文件都加进去,这样不会时不时要跑到文件头部加一行include

要在Qt中支持预编译头文件,只需要在pro文件中增加一行

1
PRECOMPILED_HEADER = ../precompile.h

其中,precompile.h是用户自己提供的一个文件。我们可以在里面包含自己想要的任何内容。比如,下面是我最常使用的预编译头文件。

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
#pragma once
#include <QtCore/qglobal.h>
#include <QDebug>
#include <QByteArray>
#include <QDebug>
#include <QDir>
#include <QElapsedTimer>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonParseError>
#include <QTextStream>
#include <QObject>
#include <QPoint>
#include <QMap>
#include <QHash>
#include <QMutex>
#include <QRect>
#include <QSize>
#include <QSettings>
#include <QString>
#include <QThread>
#include <QVariantMap>
#include <QWaitCondition>
#include <QtConcurrent>
#include <QFuture>
#include <QFutureSynchronizer>
#include <QFutureWatcher>
#include <QCoreApplication>

#include <opencv2/opencv.hpp>
#include "dbgutil.h"
#pragma execution_character_set("utf-8")

其中,文件dbgutil.h是自己定义的一个辅助公共头文件。它位于/common/include下面,为了能让预编译头文件找到它,我们需要在每个pro文件中增加下面的内容:

1
2
INCLUDEPATH += $$PWD/../common/include
DEPENDPATH += $$PWD/../common

当然也可以在预编译头文件中直接写绝对路径,都是可以的。

关闭Git插件

如果你是使用SourceTree作为Git客户端工具,那么你可能会遇到一个很讨厌的问题,就是如果通过Qt Creator添加或新建文件时,Creator总是会提问是否将文件加入Git,如果你手快没注意,或者鼠标有连击问题,你不小心点了确认,然后相当概率以后你用SourceTree的时候你会发现这个文件没有被放进去。

这个问题只有在你使用SourceTree的时候才会遇到,我比较相信是SourceTree的问题。但问题在于如何避免它。本身我不喜欢IDE里面集成配置管理功能,所以第一反应就是关掉它。

可以选择Creator的Help/About Plugins...菜单,从中选择Version Control组,并从里面去掉所有你不需要的配置工具插件。这样,以后就再也不烦你了。

到现在,我们的项目的基本骨架就搭建好了。接下来我们会逐步完善这个项目。