一个Qt4程序安装(发布)后它应该有如下的结构(可参考 Qt 程序在 windows 下的发布 ):
|-- sample.exe |-- QtCore4.dll |-- QtGui4.dll |-- imageformats/ | |-- qjpeg4.dll
接下来我们使用nsis,来制作一个实现这个功能的安装程序(并稍作完善)
如果你还没有安装nsis,不妨马上去下载一个。
第一个脚本
要使用nsis,只需要编写一个简单的 xxx.nsi 文件:
;A simple nsis script file: sample.nsi !define QTDIR "D:/Qt/4.7.0" outfile "sample-installer.exe" installDir "$PROGRAMFILES/sample" RequestExecutionLevel admin section setOutPath $INSTDIR file sample.exe file ${QTDIR}/bin/QtCore4.dll file ${QTDIR}/bin/QtGui4.dll setOutPath $INSTDIR/imageformats file ${QTDIR}/plugins/imageformats/qjpeg4.dll sectionEnd
将该文件和我们的sample.exe 放到同一个文件夹,然后点右键,选择"编译NSIS脚本"。稍等片刻,一个安装程序 sample-installer.exe 就生成了。
知识点
- 注释
- 以分号";"或井号"#"开头的行都是注释,也可以使用C语言中的"/* */"
- 编译期命令(Compile Time Commands)
-
以叹号"!"开始的命令叫做编译期命令(名字比较怪,但我们将其理解成C、C++中的宏 )
- 一定要理清这一点,它只是一种扩展。
-
- outfile
- 给安装程序指定个名字
- installDir
- 默认将文件安装到何处
-
RequestExecutionLevel
- 对Vista及Win7用户,安装程序工作时需要申请执行权限
- section
- 每一个nsis脚本至少要有一个section段。段内执行我们需要的安装操作
- setOutPath
- 指定文件输出到何处
- file
- 安装哪些文件
有没有疑问?我们一开始设置了 installDir,后面使用的却是INSTDIR。installDir 和 INSTDIR 什么关系?!
INSTDIR 的值获得方式:
- 如果脚本编译时通过 /D 选项指定了路径,将使用该路径。否则,
-
如果从注册表读取路径到 InstallDirRegKey,将使用该路径。否则,
- 才使用installDir 指定的路径
注意:用户可以通过界面选择安装路径,也是影响的INSTDIR的值
第二个脚本
前面的脚本功能太弱了:
- 用户不能选择安装目录
- 只有安装,没有卸载功能
- 不能创建快捷方式
改进一下
安装路径
安装程序相当于是一个wizard多页面程序,我们只要添加相应的页面即可。直接在第一个section的上面添加两行:
Page directory Page instfiles
这些都是nsis提供的标准页面。
添加卸载功能
添加卸载功能也就是要生成一个卸载程序,这个程序时安装程序安装时自动生成的。要实现这个,我们只需要在前面的section中添加一句
writeUninstaller $INSTDIR/uninstaller.exe
那么该卸载程序执行什么动作呢?这需要我们添加一个新的段:
section "Uninstall" rmDir /r "$INSTDIR" sectionEnd
快捷方式
常见的两类:
一个是在桌面上
createShortCut "$DESKTOP/sample.lnk" "$INSTDIR/sample.exe"
一个是在开始菜单的"程序"中。
createShortCut "$SMPROGRAMS/sample.lnk" "$INSTDIR/sample.exe"
很直观,直接放到section段中即可。程序卸载时需要删除快捷方式,故在uninstall段中需要添加形影的删除指令。
关于createShortCut的详细信息看Manual:
link.lnk target.file [parameters [icon.file [icon_index_number [start_options [keyboard_shortcut [description]]]]]]
可以设置图标、命令行参数、快捷键、描述等等。
完整代码
!define QTDIR "D:/Qt/4.7.0" outfile "sample-installer.exe" installDir "$PROGRAMFILES/sample" RequestExecutionLevel admin Page directory Page instfiles section setOutPath $INSTDIR file sample.exe file ${QTDIR}/bin/QtCore4.dll file ${QTDIR}/bin/QtGui4.dll setOutPath $INSTDIR/imageformats file ${QTDIR}/plugins/imageformats/qjpeg4.dll createShortCut "$DESKTOP/sample.lnk" "$INSTDIR/sample.exe" createShortCut "$SMPROGRAMS/sample.lnk" "$INSTDIR/sample.exe" writeUninstaller $INSTDIR/uninstaller.exe sectionEnd section "Uninstall" rmDir /r "$INSTDIR" delete "$DESKTOP/sample.lnk" delete "$SMPROGRAMS/sample.lnk" sectionEnd
知识点
page
- 用来添加页面
- 还有一个灵活一些的 pageEx 命令
- 卸载程序的页面可以使用uninstPage 命令指定
section
- 每一个NSIS安装脚本包含至少一个section
- 每一个section包含0条或多条指令
- 各个section按顺序执行(运行中可以禁用启用某些section)
- 名字为"Uninstall"或者以"un."为前缀的section,属于卸载程序
第三个脚本
第二个例子可以工作了,但是有很不爽的一点,我们还一直没提:界面好丑,好老土啊!
尽管nsis 原始自带的界面不好看,但是它也提供了新式的安装界面 (它是通过宏扩展来实现的)。使用起来也是很方便,我们看看如何做:
包含新式的头文件(我们一开始就说了,叹号开头的可以类比C、C++的宏)
!include "MUI2.nsh"
插入新式页面
!insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES
卸载页面类似(PAGE和UNPAGE的区别)
!insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES
由于新式页面提供了友好的多语言支持,我们选择简体中文
!insertmacro MUI_LANGUAGE "SimpChinese"
完整代码
!include "mui2.nsh" !define QTDIR "D:/Qt/4.7.0" outfile "sample-installer.exe" installDir "$PROGRAMFILES/sample" RequestExecutionLevel admin ;Page directory ;Page instfiles !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_LANGUAGE "SimpChinese" section setOutPath $INSTDIR file sample.exe file ${QTDIR}/bin/QtCore4.dll file ${QTDIR}/bin/QtGui4.dll setOutPath $INSTDIR/imageformats file ${QTDIR}/plugins/imageformats/qjpeg4.dll createShortCut "$DESKTOP/sample.lnk" "$INSTDIR/sample.exe" createShortCut "$SMPROGRAMS/sample.lnk" "$INSTDIR/sample.exe" writeUninstaller $INSTDIR/uninstaller.exe sectionEnd section "Uninstall" rmDir /r "$INSTDIR" delete "$DESKTOP/sample.lnk" delete "$SMPROGRAMS/sample.lnk" sectionEnd
函数
前一个已经工作的很好了,但是我们可能有其他的要求。接下来我们只看代码片段和重要的概念:
前面一直没涉及nsis的另一个重要的概念:函数(functions)。
- 函数和我们前面的段比较相似,都是包含需要执行的指令,不同之处在于section会被直接执行。而函数则需要调用。
- 按调用方式它可分两类,一是被直接调用的函数,另一类则称为回调函数。
直接调用
很简单,我们看下面的代码片段即可。
Function func ;some commands FunctionEnd Section Call func SectionEnd
回调
回调函数在特定的时候被安装程序调用,比如 .onInit 在初始化时会被调用。
这时我们可以弹出一个对话框
Function .onInit MessageBox MB_YESNO "This will install. Continue?" IDYES NoAbort Abort ; NoAbort: FunctionEnd
提示用户是否继续。
更有用的一点可能是,我们希望同一时刻只有一个安装程序在运行
Function .onInit System::Call 'kernel32::CreateMutexA(i 0, i 0, t "SAMPLE_MUTEX") i .r1 ?e' Pop $R0 StrCmp $R0 0 +3 MessageBox MB_OK|MB_ICONEXCLAMATION "Another Installer is Running!" Abort FunctionEnd
这儿用到了一个system::call,这是什么东西?
插件
在前面,我们调用了系统的api来创建一个Windows的内核对象(互斥量)。调用系统api使用的是nsis的System插件(由于不需要额外的操作,你可能没意识到它是插件)。
我们看一下Call如何使用的:
Call PROC [( PARAMS ) [RETURN [? OPTIONS]]]
对照一下!
i 0, i 0, t "SAMPLE_MUTEX" | PARAMS | 函数参数(类型和值) |
i .r1 | RETURN | 返回值 |
?e | ? OPTIONS | 选项e,可执行程序的路径 |
注意,参数和返回值,其实都是3个值一组(类型、(source)值、(destination)值)。返回值中的.代表忽略source值。
除了system插件,nsis自带的还有进行数学运算的math,自定义界面的nsDialog插件等等。
LogicLib
我们前面涉及到了 StrCmp $R0 0 +3 ,这是神马用法??
流程控制真的让人头疼,用标签和相对位置进行跳转,给人一种汇编语言的感觉。看看下面的例子:
StrCmp $0 'some value' 0 +3 MessageBox MB_OK '$$0 is some value' Goto done StrCmp $0 'some other value' 0 +3 MessageBox MB_OK '$$0 is some other value' Goto done # else MessageBox MB_OK '$$0 is "$0"' done:
一不小心数错行,或者增减语句,可都不是小事情了。
幸好,nsis可以通过类似宏的机制扩展,而且也提供了一个 logiclib.nsh 文件。这样一来,我们处理分支或循环就简单多了(尽管语法少多有点别扭,没办法,毕竟不是内置支持的东西)
${If} $0 == 'some value' MessageBox MB_OK '$$0 is some value' ${ElseIf} $0 == 'some other value' MessageBox MB_OK '$$0 is some other value' ${Else} MessageBox MB_OK '$$0 is "$0"' ${EndIf}
其他
文本只是一个学习笔记,很多东西都没有涉及(manual中很详细哈,遇到问题应该首先去查manual)。