当前位置:   article > 正文

QtIFW学习_qt ifw

qt ifw

1. 构建Qt安装程序

1.1. Qt应用程序结构

Qt打包发布文档

还有一些说明文档。如:ReadMe、License等

在 Windows 中,要解决可执行文件过大的问题,可以使用像 UPX 这样的加壳工具。在对可执行文件压缩之后,会在启动时将其解压缩到 RAM 中。

而在 Linux 中,可以通过 strip 命令来去除目标文件中的调试信息、符号信息,以减小程序的大小。但要注意一点,strip 只能用于可执行文件和动态库(.so),不能用于静态库(.a)。

1.2. 不同操作系统常用的打包工具

1.2.1. 多平台 GUI 安装程序 跨平台安装工具

  • Qt Installer Framework
    https://doc.qt.io/qtinstallerframework/
    简称 Qt IFW,由 Qt 官方提供,以前仅用于 Qt 本身,但现在已经发布了,用于创建通用的安装程序。

  • InstallBuilder
    http://installbuilder.bitrock.com/
    是一个功能强大、易于使用的跨平台安装程序创建工具。它一直都在积极维护,但仅供商业使用。

  • InstallJammer
    https://sourceforge.net/projects/installjammer/
    是一个跨平台 GUI 安装程序和生成器,虽然是开源的,但 2011 年之后就不再维护了,比较遗憾!

1.2.2. windows

  • NSIS
    https://nsis.sourceforge.io/Main_Page
    如果你喜欢开源软件,则一定知道 NSIS(全称:Nullsoft Scriptable Install System)。它是一个专业的工具,可用于创建从非常简单到非常复杂的安装程序。虽然它很小,但功能却很丰富,非常适合 Internet 分发。
    正如其名,NSIS 是基于脚本的,它能够让我们创建处理任何情况所需的复杂逻辑。幸运的是,它包含了许多插件和预定义脚本,以帮助初学者入门。

  • Inno Setup
    http://www.jrsoftware.org/isinfo.php
    是一个免费的安装制作软件,小巧、简便、精美是其最大特点,支持 pascal 脚本,能快速制作出标准 Windows 2000 风格的安装界面,足以完成一般安装任务。
    该软件用 Delphi 写成,其官网同时也提供源程序免费下载。它虽不能与 Installshield 这类“恐龙级”的安装制作软件相比,但也算是当之无愧的后起之秀。

  • Advanced Installer
    https://www.advancedinstaller.com/
    有一个免费版本,但也有其他几个版本,这些版本的价格取决于安装程序的复杂程度。如果你正在寻找更专业的东西,其中还包括一些支持选项,那么 Advanced Installer 是一个不错的选择。
    用它创建 MSI 文件包非常方便,只需添加文件、修改名称、添加按钮即可,无需任何脚本方面的知识,并且生成的安装文件保证符合 Windows 最佳操作建议。

  • InstallShield
    https://www.flexerasoftware.com/install/products/installshield.html
    是一款“恐龙级”的安装包制作工具,是 Flexera Software 的当家产品。这不仅是因为它拥有 20 多年的研发历史,而且它也是全球著名软件公司的“皇家御用”打包软件,比如 Adobe、Corel、Autodesk 等公司。
    然而这款软件过于专业,并不像 NSIS、Inno Setup 等那样容易入门,所以想学习必须下很大功夫。这也是全球领先的 Windows 安装开发解决方案,现在已经成为 Windows Installer 和 InstallScript 安装方面的行业标准。

  • WIX Toolset
    https://wixtoolset.org/
    是一个免费的打包工具,通常要与 Visual Studio(2012 或更高版本)一起使用。之所以最后提到它,是因为它需要经过大量的学习。虽然可用它创建一些非常复杂的安装程序,但要编写大量的代码并经常使用命令行。

1.2.3. Linux

对于 Linux 系统来说,要查看可执行文件依赖的库以及缺少的函数符号,可以使用 ldd -r 指令。

  • 构建源码包
    对于开源项目来说,这是最简单的方法。
    但一定要记得,在使用 tar 命令对目录树进行压缩之前,需要先运行 make distclean 来清理构建环境。

  • 创建本地分发包
    这要考虑系统的发行版,而主流的包管理系统有两个:rpm 和 deb。rpm 格式起源于 RedHat,但也被其他发行版使用,例如:CentOS、SuSE 和 Fedora。deb 格式是由 Debian 项目开发的,适用于 Debian/Ubuntu 及其衍生版。
    rpm 可参考 RPM HOWTO(http://www.tldp.org/HOWTO/RPM-HOWTO/),尤其是关于构建的部分 - Building RPMs。
    deb 可参考Debian 新维护者手册(https://www.debian.org/doc/manuals/maint-guide/)。

  • 创建独立的应用程序包
    若要将 Qt 程序作为一个独立的包部署到 Linux 中,需要将其以及所需的组件捆绑在一起,像 Qt 库、Qt 插件(尤其是 platforms 插件)。
    推荐一个 Linux 部署工具 - linuxdeployqt(https://github.com/probonopd/linuxdeployqt),它能够自动执行上述的流程,并提供 AppImage(https://appimage.org/)。

Tips:和 Windows 不同的是,Linux 下可执行文件及其依赖库放在同一目录一般是无法正常运行的。常用方式是写一个 Shell 脚本,并用 LD_LIBRARY_PATH 指定依赖库所在目录,然后通过运行这个脚本来间接地启动程序。

1.3. 静态库与动态库的区别

静态库和动态库的区别.jpg

2. 程序打包过程

@startuml
:1.构建realease版本应用程序;
:2.用打包工具添加依赖项;
:3.添加其他帮助文档到目录结构;
:4.测试程序打包是否成功;
@enduml
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

过程说明:
过程2中. 常用的打包工具

  • windeployqt:由 Qt 官方提供,旨在自动化创建可部署文件夹的过程,该文件夹包含了应用程序所需的 Qt 相关依赖项(库、插件和翻译等)。
  • Dependency Walker:用于查找程序所需的依赖库,类似的工具还有 Process Explore。

Tips:
windeployqt 并没有考虑程序依赖的第三方库(例如:OpenCV)。此外,如果使用了 MSVC 编译器,它也不会将相应的 C/C++ 运行时库拷贝进去。这时,需用Dependency Walker检查程序所链接的库文件。

对于特定的编译器,其依赖的库文件如下:

VC++ 14.0 (2015)MinGW
C 运行时:vccorlib140.dlllibwinpthread-1.dll
vcruntime140.dlllibgcc_s_dw2-1.dll
C++ 运行时:msvcp140.dlllibstdc+±6.dll

3. QtIFW 安装配置

Qt Installer Framework 简称 Qt IFW,是由 Qt 官方提供的安装程序制作框架。
Qt IFW 下载页

3.1. 下载安装对应版本,选择安装路径

bin:提供了一些基本的工具,比如打包要用的 binarycreator。
doc:包含了相应的帮助文档,有助于更好的掌握 Qt IFW。
examples:有各种各样的示例,方便我们学习研究。
Licenses:许可协议。

3.2. 配置环境变量

QtIFW配置环境变量.jpg

3.3. 在QtCreatoer添加帮助文档

QtCreator添加QtIFW帮助文档.jpg
QtIFW帮助文档所在位置.jpg

3.4. 构建examples.pro可查看各个例子安装效果

构建examples.pro例子.jpg

4. QtIFW 创建安装程序

Qt IFW 创建安装程序有一定的步骤:

4.1. 创建一个新的文件夹,里面必须包含两个文件夹config/和packages/

MySoftware根目录.jpg

4.2. config 创建配置文件config.xml

congfigXml文件.jpg

Name: 被添加到页面和简介文本中的应用程序名称
Version: 程序版本号
Title: 标题栏上的安装程序名称
Publisher: 软件的发布者(如 Windows 控制面板中所示)
StartMenuDir: Windows 开始菜单中产品的默认程序组名称
TargetDir: 程序安装的目标路径

其他参数可查看:https://doc.qt.io/qtinstallerframework/ifw-globalconfig.html

4.3. 创建包的配置信息

QtIFW框架中安装程序只需要处理一个组件,提供安装程序时安装组件可选,如:com.qtobject.ifw。 在 com.qtobject.ifw/meta 中配置package.xml。

Package_xml.jpg

DisplayName: 组件名称
Description: 组件描述信息
Version: 组件版本号
ReleaseDate: 组件发布日期
Default: 如果在安装程序中预先选择了组件,则为 true。
Script: js文件名,如本例installscriqt.qs,用于在加载时执行一些安装操作。

其他参数可查看: https://doc.qt.io/qtinstallerframework/ifw-component-description.html#package-information-file-syntax

安装组件效果如下:
组件.jpg
添加到开始菜单.jpg

4.4. 打包软件生成安装程序

  • 在com.qtobject.ifw/data中放入需打包的文件(.exe、.dll 等)。

  • 要生成安装程序,需要借助 QtIFW 提供的 binarycreator.exe 参考链接

  • 打开 CMD或 PowerShell,并进入包目录 ,然后输入
    binarycreator -c config\config.xml -p packages MySoftwareInstaller.exe -v

生成安装程序.jpg

QtIFW 在生成安装程序时,会用自带的 archivegen 工具将这些文件压缩成 7zip 格式;然后在安装时,再从压缩包中将它们提取出来。
好处:保证程序的安全性,压缩程序的大小。

4.5. 测试安装

最好使用一台没有qt开发环境的电脑测试安装程序。除了在windows系统上运行,有条件的话也可以尝试在不同的系统中生成安装程序,测试运行。

5. QtIFW 创建在线安装程序

上一节简单介绍QtIFW离线安装程序,接下来介绍QtIFW如何在线安装程序。主要区别是在于存储库的创建和配置过程。

5.1. 配置存储库

  1. 在config.xml配置存储库信息。这些信息由RemoteRepositories元素指定,它可以包含若干个Repository子元素。Repository每一个都包含以下信息:
    配置存储库.jpg

Url: 存储库的地址,指向列出可用组件列表的 Updates.xml 文件。如上图:本地Url也可以,注意格式前面要加file:///
Enabled: 若为 0,则禁用存储库。若为 1,则启用存储库。
Username: 对于需要身份验证的存储库,用来表示用户名。
Password: 对于需要身份验证的存储库,用来表示密码。
DisplayName: 设置要显示的字符串,而非 Url。

Tips:密码是以纯文本形式保存的,因此不建议在这里输入。此处如果未设置身份认证信息,将会在运行时通过对话框获取,用户可以在运行时处理这些设置。

  1. 生成安装程序
    使用binarycreator -c config\config.xml -p packages MySoftwareInstaller.exe -v 命令生成安装程序

5.2. 创建在线存储库

  1. 在package.xml中修改信息,注意版本号一定要增加
    版本号从1.0.0增加到2.0.0

package.xml更新版本号.jpg

  1. 利用 repogen 工具命令repogen -p packages repository,将包转换为安装程序能在运行时获取的文件结构。此时会生成一个名叫repository 的存储库。里面包含组件包的完整副本和Updates.xml的一些额外生成的元数据(如:SHA安全校验码)
    包的完整副本和元数据.jpg

5.3. 查看在线安装程序

  1. 在安装程序的根目录选中maintenancetool.exe(Qt 中的维护工具,用于添加/更新/删除组件)可以在线更新组件,如下图可以看见组件版本号从1.0.0增加到2.0.0,选中即可更新版本。
    更新组件.jpg

  2. 在设置中的资料档案库可以看见之前配置存储库的用户和密码,选中该存储库可以用下方的按钮“条件测试”检测存储库是否可用。

资料档案库.jpg

6. QtIFW 覆盖安装

QtIFW不支持离线升级,程序新版本目录不能直接覆盖旧版本目录安装,要先卸载。QtIFW 提供覆盖安装的方法简化这一流程。

6.1. 自动卸载旧版本

  1. 编写卸载脚本。参考链接
//我们可以通过模拟用户单击与 UI 交互,来实现最终的卸载功能
// 卸载脚本:如果程序已安装,则会调用 maintenance 工具,自动进行卸载。
function Controller()
{
    gui.clickButton(buttons.NextButton);
    gui.clickButton(buttons.NextButton);

    // 连接信号槽
    installer.uninstallationFinished.connect(this, this.uninstallationFinished);
}

// 当卸载完成时,触发
Controller.prototype.uninstallationFinished = function()
{
    gui.clickButton(buttons.NextButton);
}

// 与完成页面上的部件交互
Controller.prototype.FinishedPageCallback = function()
{
    gui.clickButton(buttons.FinishButton);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

然后用之前的安装程序测试;执行 maintenancetool.exe --script=uninstallscript.qs 命令卸载旧版本。

  1. 将卸载脚本uninstallscript.qs放入 data 目录中(例如:data/script/uninstallscript.qs),最终由 Qt IFW 打包进安装程序。当需要进行覆盖安装时,maintenancetool 工具就可以很容易的找到它。

6.2. 覆盖安装

  1. 既然是覆盖安装,必然少不了对安装位置的检测,一旦发现程序已安装,往往需要加一些友好性的提示信息(例如:显示的“检测到程序已安装,继续将会被覆盖。”)。

要完成这一步,则需要为安装程序添加自定义 UI。首先,要在 meta 目录下添加一个 targetwidget.ui 界面文件(QtDesigner可以编写ui),然后,还需要在 package.xml 文件中用 UserInterfaces元素标记它:

<?xml version="1.0" encoding="UTF-8"?>
<Package>
    <Name>MySoftware</Name>
    <Version>1.0.0</Version>
    <Title>MySoftware Installer</Title>
    <Publisher>Twy</Publisher>
    <StartMenuDir>MySoftware</StartMenuDir>
    <TargetDir>@HomeDir@/MySoftware</TargetDir>
    <UserInterfaces>
        <UserInterface>targetwidget.ui</UserInterface>
    </UserInterfaces>
</Package>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 修改安装脚本

将交互部分添加到安装脚本 installscript.qs 中。注意程序各文件的路径。

var targetDirectoryPage = null;

// 构造函数
function Component() 
{
    installer.gainAdminRights();
    component.loaded.connect(this, this.installerLoaded);
}

// 实用函数,类似于 QString QDir::toNativeSeparators()
var Dir = new function () {
    this.toNativeSparator = function (path) {
        if (installer.value("os") == "win")
            return path.replace(/\//g, '\\');
        return path;
    }
};

// 添加桌面和开始菜单快捷方式
Component.prototype.createOperations = function() 
{
    component.createOperations();
    component.addOperation("CreateShortcut",
                           "@TargetDir@/bin/MySoftware.exe",
                           "@DesktopDir@/MySoftware.lnk",
                           "workingDirectory=@TargetDir@");

    component.addOperation("CreateShortcut",
                           "@TargetDir@/bin/MySoftware.exe",
                           "@StartMenuDir@/MySoftware.lnk",
                           "workingDirectory=@TargetDir@");
}

// 加载组件后立即调用
Component.prototype.installerLoaded = function()
{
    installer.setDefaultPageVisible(QInstaller.TargetDirectory, false);
    installer.addWizardPage(component, "TargetWidget", QInstaller.TargetDirectory);

    targetDirectoryPage = gui.pageWidgetByObjectName("DynamicTargetWidget");
    targetDirectoryPage.windowTitle = "选择安装目录";
    targetDirectoryPage.description.setText("请选择程序的安装位置:");
    targetDirectoryPage.targetDirectory.textChanged.connect(this, this.targetDirectoryChanged);
    targetDirectoryPage.targetDirectory.setText(Dir.toNativeSparator(installer.value("TargetDir")));
    targetDirectoryPage.targetChooser.released.connect(this, this.targetChooserClicked);

    gui.pageById(QInstaller.ComponentSelection).entered.connect(this, this.componentSelectionPageEntered);
}

// 当点击选择安装位置按钮时调用
Component.prototype.targetChooserClicked = function()
{
    var dir = QFileDialog.getExistingDirectory("", targetDirectoryPage.targetDirectory.text);
    if (dir != "") {
        targetDirectoryPage.targetDirectory.setText(Dir.toNativeSparator(dir));
    }
}

// 当安装位置发生改变时调用
Component.prototype.targetDirectoryChanged = function()
{
    var dir = targetDirectoryPage.targetDirectory.text;
    if (installer.fileExists(dir) && installer.fileExists(dir + "/bin/MySoftware.exe")) {
        targetDirectoryPage.warning.setText("<p style=\"color: red\">检测到程序已安装,继续将会被覆盖。</p>");
    } else {
        targetDirectoryPage.warning.setText("");
    }
    installer.setValue("TargetDir", dir);
}

// 当进入【选择组件】页面时调用
Component.prototype.componentSelectionPageEntered = function()
{
    var dir = installer.value("TargetDir");
    if (installer.fileExists(dir) && installer.fileExists(dir + "/maintenancetool.exe")) {
        installer.execute(dir + "/maintenancetool.exe", "--script=" + dir + "/script/uninstallscript.qs");
    }
}
  • 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
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

7. QtIFW 实现自动升级

要实现自动更新,必须要有一个在线存储库respoitory。在 QtIFW 中,如果要实现这样的功能,可以利用 maintenancetool.exe及其两个重要选项:

--checkupdates:检测更新,并返回一个 XML。
--updater:以更新模式启动应用程序。
  • 1
  • 2

7.1. 检测更新

为了获取更新相关的信息,我们新建一个 bat 脚本,并通过以下命令将输出重定向到 checkUpdate.txt 文件中:

@echo off
maintenancetool --checkupdates > checkUpdate.txt
  • 1
  • 2

当有可用的更新时,maintenancetool 会返回一个 XML,通过bat 脚本将输出信息重定向到 checkUpdate.txt 文件中:其中包含了新版本的名称、大小、以及版本号等内容。倘若没有任何更新,将不会返回任何内容。

7.2. 以更新模式启动maintenancetool

一旦检测到有新版本存在,只需要以更新模式启动 maintenancetool 就行maintenancetool --updater,这样以来,默认就会选择【Update components】选项:剩下的具体要更新哪些组件,就交由用户选择。

7.3. 具体实现

为了启动外部程序,先简单介绍下 QProcess 类,它有两种启动方式:

一体式:start(),外部程序启动后,将随主程序的退出而退出。
分离式:startDetached(),外部程序启动后,当主程序退出时并不退出,而是继续运行。
  • 1
  • 2

参考 https://github.com/Skycoder42/QtAutoUpdater。自写Demo,

//在使用 --checkupdates 检测更新时,并不会运行 GUI,而是仅输出更新信息
//以下内容放入main.cpp中的一个函数中
QString program("../maintenancetool.exe");
QStringList checkArgs;
checkArgs << "--checkupdates";

// 检测更新
QProcess process;
process.start(program, checkArgs);

// 等待检测完成
if (!process.waitForFinished()) {
    qDebug() << "Error checking for updates.";
    return;
}

// 读取输出内容
QByteArray data = process.readAllStandardOutput();

// 没有输出意味着没有可用的更新
if (data.isEmpty()) {
    qDebug() << "No updates available.";
    return;
}
// 倘若需要特定的更新信息,应该解析输出的 XML。

//当检测到有可用的更新之后,以更新模式启动 maintenancetool

// 以分离式启动
QStringList updaterArgs;
updaterArgs << "--updater";
bool success = QProcess::startDetached(program, updaterArgs);
if (!success) {
    qDebug() << "Program startup failed.";
    return;
}

//需要注意的是,这里需要以分离式启动,因为程序需要关闭以进行更新。

//在启动成功之后,最后记得关闭程序:

// 关闭程序
qApp->closeAllWindows();
  • 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
  • 43

8. 制作一款精美的QtIFW安装程序

主要是界面的UI美化。参考样式可在github上搜索“Qt Frameless"。
在config.xml中添加QSS文件美化UI用StyleSheet等元素标记。

<WizardStyle>Classic</WizardStyle>
<StyleSheet>style.qss</StyleSheet>
<TitleColor>#b1b1b1</TitleColor>
  • 1
  • 2
  • 3

9. 参考资料

  • github地址(含examples) https://github.com/Waleon/QtIFW.git
  • 公众号:高效程序员 教程链接
    -打包发布教程.jpg
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/112696
推荐阅读
相关标签
  

闽ICP备14008679号