设为首页 收藏本站

NSIS中文论坛

查看: 3124|回复: 6

使用NSIS制作便携软件:以PotPlayer Portable为例

[复制链接]
  • TA的每日心情
    奋斗
    2018-11-9 07:57
  • 签到天数: 67 天

    [LV.6]常住居民II

    98

    主题

    447

    帖子

    5198

    积分

    积分
    5198
    发表于 2015-5-16 23:45:10 | 显示全部楼层 |阅读模式
    以下内容转自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

    主题

    43

    帖子

    916

    积分

    积分
    916
    发表于 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

    主题

    447

    帖子

    5198

    积分

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

    使用道具 举报

    该用户从未签到

    0

    主题

    1

    帖子

    14

    积分

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

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2019-4-24 04:30 , Processed in 0.028778 second(s), 18 queries , MemCache On.

    Powered by Discuz!X3.3

    Copyright © 2015-2018 NSIS中文论坛

    快速回复 返回顶部 返回列表