以下内容转自www.portableappc.com

前言
我推荐大家使用P.A.L来制作便携软件,重新制作轮子是没有必要的。关于便携软件的多个难点,例如多实例、进程控制、非正常退出恢复、临时复制到本地运行、环境变量、路径替换等等,在P.A.L中都有相当成熟的解决方案,用户只用填写一个ini,而不需要考虑便携软件是怎样运行的。省时省力、稳定可靠是其优点。但是,有的时候我们不得不编写一段自定义代码 (custom code)才能实现某些要求,或者你是个代码狂人,喜欢看到自己指尖的代码编译成屏幕上运行的程序,那么就必须接触P.A.L的母体——NSIS。

Nullsoft 脚本安装系统(Nullsoft Scriptable Install System)是一个开放源代码的安装程序制作工具,(Nullsoft 也是WinAmp的制作方)。使用一种制作安装程序的语言来制作一种从不需要安装的程序——PortableApps,再适合不过了。因为从本质来讲,Portableapps的运行原理,其实就是在程序启动时进行一次“安装”,在程序结束后进行一次“卸载”(当然这个时间一般非常快)。PortableApps Launcher的主要对象无非是注册表项与文件,而处理这些,正是NSIS的长项。NSIS语言简明易懂,特别擅长处理windows系统中的进程、注册表与文件。以下以PotPlayer Portable为例,浅谈使用NSIS制作便携软件的一般流程。

要做什么?
通过虚拟机观察得知,当PotPlayerMini.exe同目录存在PotPlayerMini.ini时,PotPlayer将配置保存在此ini中,否则,则将配置保存在注册表中。

因此,我们可以有两种选择:让PotPlayer保存配置在注册表,并在结束时导出为reg文件;或者保证其运行时目录下存在PotPlayerMini.ini,在结束后将PotPlayerMini.ini移至 Data 目录(程序与配置分离原则)。后一种选择的优点是,PotPlayer Portable 可以与系统中安装的PotPlayer 或其它位置的PotPlayer Portable 同时运行,不会互相干扰。因此我选择了保存配置进 ini 的方案。

另外,我希望PotPlayer Portable首次运行时显示XMP-Gray-Tab 这个皮肤,而不是默认的棒子文皮肤。因此,需要创建一个DefaultData。

前期准备:
创建 PotPlayerPortable 文件夹,创建 App\AppInfo ,App\DefaultData , App\PotPlayer ,Sources 。

将PotPlayer 程序文件放在 App\PotPlayer 目录中。

在 App\DefaultData 中创建 PotPlayerMini.ini ,写入:
  1. [Settings]
  2. LastSkinName=[smilefly]XMP-Gray-Tab.dsf ;定义默认皮肤
复制代码
提取 PotPlayer 图标,保存为:App\AppInfo\appicon.ico 。

在 Source\中创建 PotPlayerPortable.nsi ,开始敲代码吧。以下分为3段讲解这个nsi文件。

定义变量:
将固定的文件、注册表项以变量的形式在文件头部声明,不但可以少敲些文字,也有利于减少笔误。
  1. ; David Pi
  2. ; PortableAppC.com
  3. ; **************************************************************************
  4. ; === 定义变量 ===
  5. ; **************************************************************************
  6.         !define VER                 "1.5.29599.0"                         ; launcher 的版本
  7.         !define APPNAME         "PotPlayer"                         ; 程序全名
  8.         !define APP                 "PotPlayer"                         ; 程序短名称(不带空格)
  9.         !define APPEXE                 "PotPlayerMini.exe"         ; 主程序
  10.         !define APPDIR                 "App\PotPlayer"                         ; 主程序路径
  11.         !define APPSWITCH         ``                                                 ; 默认运行参数
  12. ;--- 声明注册表 ---
  13.         !define REGKEY1 "HKEY_CURRENT_USER\Software\Daum"
  14.         ;其实对于 PotPlayer Portable 的制作并不需要修改注册表,作为示例而加上。
  15. ; ---声明目录 ---
  16.         !define LOCALDIR1 "$EXEDIR\App\PotPlayer\Capture"
  17.         !define PORTABLEDIR1 "$EXEDIR\Data\Capture"
  18.         ; 截图目录,保存到Data\
  19.         !define LOCALDIR2 "$EXEDIR\App\PotPlayer\log"
  20.         ; 日志目录,结束后清除
  21.         !define LOCALDIR3 "$EXEDIR\App\PotPlayer\Playlist"
  22.         !define PORTABLEDIR3 "$EXEDIR\Data\Playlist"
  23.         ; 播放列表,保存到Data\
  24. ; ---声明文件 ---
  25.         !define LOCALFILE1 "$EXEDIR\App\PotPlayer\PotPlayerMini.ini"                 ;运行时位置
  26.         !define PORTABLEFILE1 "$EXEDIR\Data\settings\PotPlayerMini.ini"         ;关闭后保存位置
  27.         !define DEFAULTFILE1 "$EXEDIR\App\DefaultData\PotPlayerMini.ini"         ;默认设置
  28.         !define STATUS "$EXEDIR\Data\settings\${APP}Portable-RunningStatus.ini" ;这个文件,是判断便携软件是否正在运行、以及上一次是否正常退出的依据。
  29. ; **************************************************************************
  30. ; === 压缩选项 ===
  31. ; **************************************************************************
  32. SetCompressor /SOLID lzma
  33. SetCompressorDictSize 32
  34. ; **************************************************************************
  35. ; === 基本信息 ===
  36. ; **************************************************************************
  37. Name "${APPNAME} Portable"
  38. OutFile "..\${APP}Portable.exe"
  39. Icon "..\App\Appinfo\AppIcon.ico"
  40. ; **************************************************************************
  41. ; === 版本信息 ===
  42. ; **************************************************************************
  43. Caption "${APPNAME} Portable"
  44. VIProductVersion "${VER}"
  45. VIAddVersionKey ProductName "${APPNAME} Portable"
  46. VIAddVersionKey Comments "Allows ${APPNAME} to be run from a removable drive."
  47. VIAddVersionKey CompanyName "PortableAppC.com"
  48. VIAddVersionKey LegalCopyright "David Pi"
  49. VIAddVersionKey FileDescription "${APPNAME} Portable"
  50. VIAddVersionKey FileVersion "${VER}"
  51. VIAddVersionKey ProductVersion "${VER}"
  52. VIAddVersionKey InternalName "${APPNAME} Portable"
  53. VIAddVersionKey LegalTrademarks ""
  54. VIAddVersionKey OriginalFilename "${APP}Portable.exe"
  55. ; **************************************************************************
  56. ; === 头文件 ===
  57. ; **************************************************************************
  58. !include "LogicLib.nsh"
  59. !include "Registry.nsh"
  60. !include "TextFunc.nsh"
  61. !insertmacro GetParameters
  62. ; **************************************************************************
  63. ; === 其它运行选项 ===
  64. ; **************************************************************************
  65. WindowIcon Off
  66. SilentInstall Silent
  67. AutoCloseWindow True
  68. RequestExecutionLevel user
复制代码

运行阶段:
  1. ; **************************************************************************
  2. ; ==== 运行 ====
  3. ; **************************************************************************
  4. Section "Main"
  5. ;CheckDirExe:
  6.         IfFileExists "$EXEDIR\${APPDIR}\${APPEXE}" +3
  7.         MessageBox MB_OK|MB_ICONEXCLAMATION `${APPNAME} Portable无法启动,请重新安装${APPNAME} Portable。$\n错误:找不到$EXEDIR\${APPDIR}\${APPEXE}。`
  8.         Abort
  9. ;CheckRunExe:
  10.         FindProcDLL::FindProc "${APPEXE}"
  11.                 ${If} $R0 == "0"
  12.                         Goto CheckGoodExit ;无第二实例,正常启动
  13.                 ${Else}
  14.                         MessageBox MB_OK|MB_IconStop "另一个${APPNAME}正在运行,请在执行${APPNAME} Portable前关闭${APPNAME}!"
  15.                         Abort
  16.                 ${Endif}
  17. CheckGoodExit:
  18.         IfFileExists "${STATUS}" 0 SplashLogo ;假如正常退出,此文件不应该存在
  19.         MessageBox MB_OK|MB_ICONEXCLAMATION `上一次${APPNAME} Portable在本机结束时未能执行数据恢复,现在将执行恢复并重新启动。`
  20.         Call Restore
  21. SplashLogo:
  22.         ReadINIStr $0 "$EXEDIR\${APP}Portable.ini" "${APP}Portable" "DisableSplashScreen"
  23.         StrCmp $0 "true" Backup
  24.         WriteINIStr "$EXEDIR\${APP}Portable.ini" "${APP}Portable" "DisableSplashScreen" "false"
  25.         InitPluginsDir
  26.         File /oname=$PLUGINSDIR\splash.bmp "Splash.bmp"
  27.         newadvsplash::show /NOUNLOAD 1000 300 200 1 /L $PLUGINSDIR\splash.bmp
  28. Backup:
  29.         CreateDirectory "$EXEDIR\Data\settings"
  30.         WriteINIStr "${STATUS}" "${APP}Portable" "DeadInPeace" "false" ;写入状态文件
  31.         ;备份本地注册表:
  32.                 {registry::DeleteKey} "${REGKEY1}-BackupBy${APP}Portable" $R0
  33.                 ${registry::MoveKey} "${REGKEY1}" "${REGKEY1}-BackupBy${APP}Portable" $R0
  34.                 Sleep 50
  35.         ;恢复便携软件注册表:
  36.                 ${registry::RestoreKey} "$EXEDIR\Data\settings\${APP}.reg" $R0
  37.                 Sleep 50
  38.         ;清扫工作,假如用户直接运行了App\PotPlayer中的程序,就会生成这些文件,影响后面的工作:
  39.                 RMDir /r ${LOCALDIR1}
  40.                 RMDir /r ${LOCALDIR2}
  41.                 RMDir /r ${LOCALDIR3}
  42.                 Delete ${LOCALFILE1}
  43.         ;恢复本地文件:
  44.                 IfFileExists "${PORTABLEFILE1}" +2
  45.                 CopyFiles /silent "${DEFAULTFILE1}" "$EXEDIR\Data\settings" ;首次运行时,复制默认配置
  46.                 Rename "${PORTABLEDIR1}" "${LOCALDIR1}"
  47.                 Rename "${PORTABLEDIR3}" "${LOCALDIR3}"
  48.                 Rename "${PORTABLEFILE1}" "${LOCALFILE1}" ;将用户配置移动到程序目录中去
  49. ;启动主程序:
  50.         SetOutPath "$EXEDIR\${APPDIR}"
  51.         ${GetParameters} $R0
  52.         ExecWait `"$EXEDIR\${APPDIR}\${APPEXE}"${APPSWITCH} $R0` ;等待程序结束
  53. ;程序结束,开始恢复
  54.         Call Restore
  55. End:
  56.         ${registry::Unload}
  57.         newadvsplash::stop /WAIT
  58. SectionEnd
复制代码

恢复工作:
  1. Function Restore
  2. ;导出注册表:
  3.         Delete "$EXEDIR\Data\settings\${APP}.reg"
  4.         CreateDirectory "$EXEDIR\Data\settings"
  5.         {registry::SaveKey} "${REGKEY1}" "$EXEDIR\Data\settings\${APP}.reg" "/A=1" $R0
  6. ;恢复注册表:
  7.         ${registry::DeleteKey} "${REGKEY1}" $R0
  8.         ${registry::MoveKey} "${REGKEY1}-BackupBy${APP}Portable" "${REGKEY1}" $R0
  9.         ${registry::DeleteKeyEmpty} "${REGKEY1}" $R0
  10. ;备份用户配置:
  11.         RMDir /r "${PORTABLEDIR1}"
  12.         Rename "${LOCALDIR1}" "${PORTABLEDIR1}"
  13.         RMDir /r "${PORTABLEDIR3}"
  14.         Rename "${LOCALDIR3}" "${PORTABLEDIR3}"
  15.         RMDir /r "${PORTABLEFILE1}"
  16.         Rename "${LOCALFILE1}" "${PORTABLEFILE1}"
  17.         RMDir /r "${LOCALDIR2}" ;清除Log文件
  18.         Delete "${STATUS}" ;正常退出,删除状态文件
  19. FunctionEnd
复制代码
Restore 函数:之所以不将 Restore 写入 Section ,而是写成 Function ,是为了方便“CheckGoodExit”段的调用。

完成:
就是这么简单。打开NSIS,选择 Compile NSI scripts ,把保存的nsi文件拖进去,编译成功后,在 PotPlayerPortable 根目录则会生成 PotPlayerPortable.exe。

回复

使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

  • TA的每日心情
    郁闷
    2016-6-22 15:53
  • 签到天数: 1 天

    [LV.1]初来乍到

    2

    主题

    22

    帖子

    520

    积分

    积分
    520
    发表于 2015-5-19 10:35:17 | 显示全部楼层
    沙发
    沙发
    回复

    使用道具 举报

  • TA的每日心情

    2017-10-12 13:43
  • 签到天数: 18 天

    [LV.4]偶尔看看III

    12

    主题

    44

    帖子

    924

    积分

    积分
    924
    发表于 2015-5-19 11:44:34 | 显示全部楼层
    板凳
    不错,对于一些小软件来说,很实用。
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    2016-3-5 14:16
  • 签到天数: 12 天

    [LV.3]偶尔看看II

    2

    主题

    22

    帖子

    189

    积分

    积分
    189
    发表于 2015-12-21 10:28:33 | 显示全部楼层
    地板
    地板。。。
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    2018-11-9 07:57
  • 签到天数: 67 天

    [LV.6]常住居民II

    98

    主题

    450

    帖子

    5307

    积分

    积分
    5307
     楼主| 发表于 2018-4-27 13:02:36 | 显示全部楼层
    5#
    回复

    使用道具 举报

    该用户从未签到

    0

    主题

    1

    帖子

    14

    积分

    积分
    14
    发表于 2019-3-14 15:08:17 | 显示全部楼层
    6#
    能以ps为例么 再示范个么
    回复 支持 反对

    使用道具 举报

    Archiver|手机版|小黑屋|联系我们|NSIS中文论坛    

      © 2015-2019 nsisfans.com