前面的几篇文章介绍了 NSIS 的传统界面的安装包和现代界面的安装包的制作方法,也提到了 NSIS 支持自定义页面(即使用page custom
)的特性,自定义页面需要用户自己创建对话框、控件、添加控件响应等等,虽然 NSIS 提供了nsDialogs.nsh
来支持这些功能,但使用起来还是不太方便(需要专门了解这个插件诸多用法),而且不够灵活,所以本文介绍一种终极的自定义界面的安装包解决方案,即完全使用第三方界面库来绘制安装包界面。
该方案是对界面库没有限制的,可以使用其他任何界面库,如 MFC, Qt,WTL 等。通过这种方案可以很轻松的实现类似金山毒霸、QQ、360 安全卫士等软件的安装包界面。
一、原理
NSIS 自定义页面的语法:
1 | page custom [创建函数] [离开函数] [标题] |
使用第三方界面库完全定制安装包界面的基本原理就是:新建一个 dll 插件,在page custom
的[创建函数]
中调用该插件中的函数来显示界面,这时界面上的按钮的响应就不再由 NSIS 控制了,完全由我们的代码控制。
二、难点问题
使用我们的插件 dll 完全替代 NSIS 界面之后,有几个问题需要解决:
- 如何获取安装和卸载的进度
- 如何从 C++回调 NSIS 函数
2.1 安装和卸载进度
NSIS 中的安装和卸载进度由!insertmacro MUI_PAGE_INSTFILES
或Page instfiles
提供。
在完全使用自己的界面之后,这 2 个 NSIS 界面都不能使用了,这时我们需要自己获取安装(释放)和卸载(删除)的进度。
以安装进度为例,NSIS 中文件的安装时文件释放功能都是由File
命令提供,但该命令没有提供释放进度,所以我们无法获取到实时的释放进度。在这里我们可以使用一个曲折的方法,我们将一个 7z 压缩包放入安装包中:
1 | SetOutPath $INSTDIR |
等安装包释放完这个压缩包之后(这段时间的进度无法显示),再使用 NSIS 官方提供的nsis7z
插件来解压缩这个 7z 压缩包,由于 nsis7z 插件可以提供解压缩进度,所以我们可以将这个进度显示在安装进度页面上,解压完之后再删除 7z 压缩包。这个方案的一个弊端就是,7z 压缩包从安装包中释放到本地磁盘的过程需要时间,且这个时间无法准确的显示在进度页面。
1 | Function ExtractFunc |
1 | Function ExtractCallback |
写这篇文字的时候,发现现在的nsis7z已经太老了,新版的压缩软件生成的 7z 压缩包,该插件已经无法解压。可以使用 7za.exe 命令行工具来生成 7z 压缩文件,7za.exe 从此处下载:http://download.csdn.net/download/china_jeffery/10214464。
7za 生成 7z 压缩包语法为:7za.exe a app.7z app\*
2.2 从 C++回调 NSIS 函数
比如用户点击了我们自定义界面上的“取消”按钮,这时我们需要调用 NSIS 的Abort
函数来取消安装。此时就需要解决如何从 C++环境回调到 NSIS 环境。
我们可以使用NSIS教程(8)-插件开发中介绍的PluginCommon.h
来实现该功能。
大致原理是,在 NSIS 脚本中初始化自定义界面的控件与 NSIS 函数指针(整型)的绑定关系(如控件名–函数名),当用户点击控件之后,查找到该控件绑定的 NSIS 函数,然后调用extra_parameters::ExecuteCodeSegment
函数(函数第一个参数就是 NSIS 函数指针)。
以 duilib 界面库为例,对 NSIS 暴露 OnControlBindNSISScript 接口,提供绑定控件与 NSIS 函数指针(整型)的功能:
1 | NSISAPI OnControlBindNSISScript(HWND hwndParent, int string_size, char *variables, stack_t **stacktop, extra_parameters *extra) |
在 NSIS 中调用OnControlBindNSISScript
绑定控件与 NSIS 函数:
1 | GetFunctionAddress $0 OnExitDUISetup |
在 duilib 的Notify
按钮事件响应函数中调用ExecuteCodeSegment
执行 NSIS 函数:
1 | void CDlgMain::Notify( TNotifyUI& msg ) |
可以参考我的NSIS-UI-Plugin 项目,基于该项目可以使用任意第三方界面库来定制安装界面。