
简介本资源面向Windows桌面开发与安装包制作人员提供一套基于NSIS与Duilib的自定义安装程序完整工程解决传统安装界面简陋、交互体验差的问题适合具备一定脚本与C基础的开发者进阶使用。压缩包共451个文件约9.6MB涵盖113个nsh脚本、113个png界面素材、58个nlf多语言文件、35个ico图标、32个bmp位图、29个dll动态库、24个xml布局及17个exe工具等另有nsi主脚本、bat构建批处理与chm帮助文档构成从皮肤资源到编译产物的完整链路。已有2942人学习下载。通过该工程可掌握NSIS脚本编写、Duilib的XML界面布局与皮肤定制、DLL集成调用、快捷方式与开机启动项写入、多语言适配及错误日志处理等关键环节并借助现成构建脚本快速生成美观且功能完备的安装程序提升软件首次安装的用户体验。1. 为什么我劝你放弃默认界面NSISDuilib 自定义安装程序能解决什么如果你做过 Windows 桌面软件交付大概率被 NSIS 默认那套灰扑扑的安装向导折磨过。功能上它没问题脚本写几行就能打包出一个能跑的 exe但界面停留在 Win98 审美客户拿到手第一反应是“这软件靠谱吗”。更麻烦的是默认 UI 的控件布局、按钮文案、品牌露出几乎没法改想加个自定义协议注册页、想做个带背景图的欢迎页Modern UI 也只能给你换个皮肤动不了骨架。NSISDuilib 这套组合就是冲着这个痛点来的。NSIS 负责安装逻辑——文件释放、注册表写入、快捷方式、卸载信息、静默安装参数这些它干了二十多年稳。Duilib 负责界面渲染——它是一个轻量级 C 界面库支持 XML 描述布局、贴图、透明、自绘控件国内不少客户端软件都用它做皮肤系统。把两者接起来你就能得到一个逻辑用 NSIS 脚本控制、界面完全自己画的安装程序。适合谁看正在做 Windows 客户端交付、被默认安装界面拖累品牌观感、又不想为了一个安装器去学 WiX 或自己写 Win32 窗口的工程师。如果你只需要一个能双击下一步的安装包NSIS 默认界面够用不必折腾但如果你需要安装器承担品牌展示、协议确认、组件选择甚至简单的环境检测反馈这套方案值得花时间拆一遍。2. 环境搭建与 NSIS 脚本骨架从零跑通第一个自定义窗口2.1 工具链准备与版本选择先把工具链凑齐。NSIS 本体建议用 3.08 或更高版本2.x 对 Unicode 支持不完整Duilib 的 XML 里写中文容易出乱码。下载安装后把安装目录下的makensis.exe路径加进系统环境变量后面命令行编译会用到。Duilib 这边原版在 GitHub 上已经多年不更新国内常用的是带修复的分支版本编译方式分两种直接用现成的静态库或者把源码拖进 VS 自己编。我一般选后者因为安装器体积敏感自己编可以裁掉用不到的控件模块。编译 Duilib 时注意两点一是字符集选 Unicode二是运行库选静态链接/MT否则目标机器没装对应 VC 运行库时安装器直接起不来这个坑后面还会细说。编完你会得到DuiLib.lib和一组头文件把它们放到 NSIS 工程能引用的目录下。NSIS 侧需要额外准备一个插件NSIS-Duilib-Plugin或者自己封装的桥接 DLL。它的作用是在 NSIS 脚本里调用 Duilib 创建窗口、加载 XML、处理消息循环。没有这个桥接层NSIS 和 Duilib 是两个世界的东西接不上。2.2 NSIS 脚本最小骨架先写一个能编译通过、能弹出 Duilib 窗口的最小脚本别一上来就堆功能。骨架长这样; installer.nsi - 最小可运行骨架 Unicode true Name MyApp Setup OutFile MyAppSetup.exe InstallDir $PROGRAMFILES\MyApp RequestExecutionLevel admin ; 引入 Duilib 桥接插件 !addplugindir .\plugins Section MainSection SEC01 ; 调用插件创建 Duilib 窗口传入 XML 路径 Duilib::CreateWindow $PLUGINSDIR\skin\main.xml Pop $0 ; $0 返回窗口句柄或错误码 ${If} $0 error MessageBox MB_ICONSTOP 界面加载失败 Abort ${EndIf} ; 进入消息循环等待用户操作 Duilib::MessageLoop Pop $1 ; $1 为用户点击的按钮 ID比如 install / cancel ${If} $1 install SetOutPath $INSTDIR File /r .\payload\*.* WriteUninstaller $INSTDIR\uninst.exe ${EndIf} SectionEnd Section Uninstall Delete $INSTDIR\uninst.exe RMDir /r $INSTDIR SectionEnd这段脚本的逻辑说明Unicode true开启 Unicode 编译避免中文乱码!addplugindir告诉编译器插件 DLL 在哪Duilib::CreateWindow是桥接插件暴露的函数参数是 XML 皮肤文件路径返回句柄或错误标识Duilib::MessageLoop阻塞等待用户交互返回按钮 ID。参数怎么改XML 路径建议放$PLUGINSDIR这是 NSIS 的临时目录安装结束自动清理不会在用户机器上留垃圾。按钮 ID 的映射关系在 XML 里定义后面会讲。编译命令就一行makensis installer.nsi如果报Duilib::CreateWindow未找到检查插件 DLL 是否放进了plugins目录且位数和 NSIS 一致32 位 NSIS 配 32 位插件。2.3 Duilib XML 皮肤文件结构Duilib 的界面全靠 XML 描述。一个典型的安装器 XML 分三层窗口定义、布局容器、控件。最小可用版本!-- main.xml - 安装器主界面 -- Window size600,400 caption0,0,600,32 mininfo600,400 VerticalLayout bkcolor#FFFFFFFF !-- 顶部标题栏自绘实现拖动和关闭 -- HorizontalLayout height32 bkcolor#FF2D8CF0 Label textMyApp 安装向导 textcolor#FFFFFFFF font1 padding12,0,0,0/ Control / Button namebtn_close bkimageclose.png width32 height32/ /HorizontalLayout !-- 中间内容区放协议或组件选择 -- VerticalLayout inset20,20,20,20 Label text欢迎使用 MyApp 安装程序 font2 textcolor#FF333333 height40/ RichEdit namelicense height200 bkcolor#FFF5F5F5 readonlytrue/ /VerticalLayout !-- 底部按钮区 -- HorizontalLayout height50 inset0,0,20,0 Control / Button namebtn_install text开始安装 bkimagebtn_normal.png width100 height32/ Button namebtn_cancel text取消 bkimagebtn_normal.png width80 height32/ /HorizontalLayout /VerticalLayout /Window逻辑说明Window的caption属性设为0,0,600,32表示把顶部 32 像素区域定义为可拖动标题栏这是自绘窗口实现拖动的关键。name属性是控件的唯一标识NSIS 侧通过这个 name 拿到按钮点击事件。bkimage引用的是皮肤目录下的图片资源路径相对于 XML 文件位置。参数怎么改size控制窗口初始尺寸mininfo控制最小尺寸防止用户拖太小把布局挤烂。inset是内边距格式是左、上、右、下。字体font1对应的是在资源文件里注册的字体索引需要在代码里预先加载字体资源否则文字不显示。3. NSIS 与 Duilib 的桥接机制消息循环、控件回调与数据传递3.1 桥接层到底做了什么很多人卡在这一步NSIS 脚本和 Duilib 窗口怎么通信NSIS 是解释执行的脚本引擎Duilib 是 C 窗口库两者没有天然通道。桥接插件的本质是一个 DLL它导出几个 NSIS 能调用的函数内部启动 Duilib 的消息循环把用户操作翻译成 NSIS 能读的返回值。具体流程NSIS 调用CreateWindow插件内部初始化 Duilib、加载 XML、创建窗口然后返回。NSIS 再调用MessageLoop插件进入CPaintManagerUI::MessageLoop()这时候窗口才真正响应鼠标键盘。用户点击按钮时Duilib 触发Notify回调插件在回调里记录按钮 name然后退出消息循环把 name 作为返回值交给 NSIS。NSIS 拿到返回值后用${If}判断走哪个分支。这个机制决定了两个约束一是消息循环期间 NSIS 脚本是阻塞的你不能在窗口开着的时候同时跑 NSIS 的其他指令二是所有界面逻辑必须由按钮点击驱动没有实时双向通信。想清楚这一点后面设计交互流程就不会拧巴。3.2 控件回调与按钮 ID 映射Duilib 的按钮点击通过Notify回调处理。在桥接插件的 C 代码里典型实现// 桥接插件中的回调处理 void CFrameWnd::Notify(TNotifyUI msg) { if (msg.sType _T(click)) { CDuiString name msg.pSender-GetName(); if (name _T(btn_install)) { m_result _T(install); // 记录结果 ::PostQuitMessage(0); // 退出消息循环 } else if (name _T(btn_cancel)) { m_result _T(cancel); ::PostQuitMessage(0); } else if (name _T(btn_close)) { m_result _T(close); ::PostQuitMessage(0); } } }逻辑说明msg.sType click过滤点击事件msg.pSender-GetName()拿到触发事件的控件 name和 XML 里定义的 name 对应。PostQuitMessage(0)让消息循环退出控制权回到 NSIS。m_result是插件内部成员MessageLoop返回时把它作为返回值 Pop 给 NSIS 脚本。参数怎么改如果你要加一个“浏览安装路径”的按钮在 XML 里加Button namebtn_browse .../在回调里加一个分支把m_result设成browseNSIS 侧收到后调用nsDialogs::SelectFolderDialog或者直接用FileRequest让用户选目录选完再调一次CreateWindow刷新界面显示。注意每次MessageLoop退出后窗口就销毁了想保持界面状态需要把数据存到插件侧或 NSIS 变量里。3.3 安装进度与界面刷新安装过程中要显示进度条这是刚需。但前面说了消息循环是阻塞的安装文件释放的时候窗口没法刷新。常见做法是把安装逻辑拆成多个 Section每个 Section 开始前更新一次界面文字或者用 Duilib 的定时器在桥接层做假进度动画。更实用的方案是用 NSIS 的DetailPrint配合插件暴露的SetProgress函数; 分段安装每段更新进度 Duilib::SetProgress 10 SetOutPath $INSTDIR File .\payload\app.exe Duilib::SetProgress 40 File .\payload\config.ini Duilib::SetProgress 70 WriteRegStr HKLM Software\MyApp InstallPath $INSTDIR Duilib::SetProgress 100插件侧的SetProgress直接操作 Duilib 进度条控件的SetValue因为此时消息循环已经退出需要手动调Invalidate强制重绘。这个方案不完美进度条是跳变的不是平滑的但胜在实现简单、稳定。想要平滑进度就得把安装逻辑放到独立线程桥接层做线程同步复杂度上一个台阶一般安装器没必要。4. 避坑与排查自定义安装程序最容易翻车的五个地方4.1 安装器在目标机器上闪退现象开发机编译运行正常拷到测试机双击后窗口一闪就没了或者直接报“应用程序无法正常启动”。原因九成是运行库问题。Duilib 和桥接插件如果动态链接了 VC 运行库/MD目标机器没装对应版本的 Redistributable 就会加载失败。NSIS 本体是静态编译的但插件 DLL 不一定。解决编译 Duilib 和桥接插件时统一改成静态链接/MT 或 /MTd 对应 Release/Debug在 VS 项目属性 → C/C → 代码生成 → 运行库里改。改完重新编译用 Dependency Walker 或dumpbin /dependents检查插件 DLL 是否还依赖msvcp140.dll之类的。确认干净后再打包。4.2 XML 加载失败但没有任何报错现象调用CreateWindow后窗口不出现NSIS 侧收到error返回值但不知道具体哪里错了。原因Duilib 加载 XML 失败时默认不弹错误框静默返回失败。常见触发点XML 路径写错、XML 里有语法错误、bkimage引用的图片文件不存在、字体资源没注册。解决在桥接插件的CreateWindow实现里加日志把CPaintManagerUI::LoadSkin的返回值打出来同时用OutputDebugString输出当前工作目录和拼接后的完整路径。图片缺失的问题把皮肤目录整个用File /r打进$PLUGINSDIR确保相对路径能对上。字体问题在插件初始化时调AddFontResource或AddFontMemResourceEx注册字体文件。4.3 中文显示成乱码或方块现象界面上的中文变成问号、方块或者乱码字符。原因三个可能——NSIS 脚本没开Unicode trueDuilib 编译时用了多字节字符集XML 文件保存的编码不是 UTF-8。解决NSIS 脚本第一行加Unicode trueVS 项目属性里字符集选“使用 Unicode 字符集”XML 文件用记事本或 VS 另存为 UTF-8 格式注意不要带 BOMDuilib 对 BOM 的处理不一致有的版本会多出一个不可见字符导致解析失败。三个地方都对齐后中文就正常了。4.4 安装路径带空格时文件释放失败现象默认安装到C:\Program Files\MyApp没问题用户改成D:\My Software\App后部分文件没释放或者注册表写入失败。原因NSIS 里路径变量没加引号或者 Duilib 侧回传的路径字符串被截断。NSIS 的File指令对带空格路径的处理依赖SetOutPath是否正确设置如果路径变量拼接时少了引号空格后面的部分会被当成另一个参数。解决所有涉及路径的 NSIS 指令变量一律用引号包起来比如SetOutPath $INSTDIR、File /oname$INSTDIR\app.exe .\payload\app.exe。Duilib 侧回传路径时确保字符串完整不要用固定长度缓冲区截断。测试时专门用带空格和中文的路径跑一遍。4.5 卸载时残留文件和注册表现象通过控制面板卸载后安装目录还在注册表项也没清干净重装时旧配置干扰新版本。原因卸载 Section 里只删了主程序没处理安装时创建的子目录、日志文件、注册表项。或者RMDir没用/r参数非空目录删不掉。解决安装时把所有创建的资源列个清单卸载时逐项清理。注册表用DeleteRegKey和DeleteRegValue成对处理。目录删除用RMDir /r $INSTDIR但注意这会递归删除整个目录确保$INSTDIR确实是你的安装目录再执行。卸载完成后可以加一个MessageBox提示或者直接静默退出。5. 进阶技巧让安装器看起来不像“安装器”5.1 无边框窗口与自定义拖动区域默认的 Duilib 窗口带系统边框一眼就能看出是传统安装程序。去掉边框在 XML 的Window标签里加sizebox0,0,0,0和caption0,0,600,32前者去掉可调整大小的边框后者把顶部区域定义为拖动区。这样窗口没有系统标题栏全靠自绘视觉上更像一个独立应用而不是安装向导。拖动区的实现细节caption属性告诉 Duilib 哪个区域响应鼠标拖动格式是“左,上,右,下”。设成0,0,600,32表示顶部 32 像素高的整条区域都可以拖动窗口。如果你在标题栏放了按钮按钮区域会自动排除拖动响应不用担心点按钮变成拖窗口。5.2 背景贴图与圆角窗口想要圆角或异形窗口Duilib 支持通过贴图实现。准备一张带透明通道的 PNG 作为窗口背景在 XML 里给根布局设bkimageWindow size600,400 caption0,0,600,32 roundcorner8,8,8,8 bktranstrue VerticalLayout bkimagebg.png inset8,8,8,8 !-- 内容区 -- /VerticalLayout /Windowroundcorner是 Duilib 内置的圆角属性四个值对应四个角的半径。bktranstrue开启背景透明配合 PNG 的 alpha 通道实现异形边缘。注意inset要留出圆角对应的边距否则内容会盖住圆角区域。这个方案在 Win7 上可能有锯齿Win10 以上表现良好。5.3 静默安装与命令行参数企业部署场景需要静默安装不弹界面。在 NSIS 脚本里判断命令行参数; 检查是否带 /S 参数NSIS 内置静默参数 ${If} ${Silent} ; 静默模式跳过 Duilib 窗口直接安装 SetOutPath $INSTDIR File /r .\payload\*.* WriteUninstaller $INSTDIR\uninst.exe ; 写入静默安装标记供后续查询 WriteRegStr HKLM Software\MyApp SilentInstall 1 ${Else} ; 交互模式走 Duilib 界面 Duilib::CreateWindow $PLUGINSDIR\skin\main.xml ; ... 后续逻辑 ${EndIf}逻辑说明${Silent}是 NSIS 逻辑库提供的判断检测命令行是否带/S。静默模式下不创建窗口直接执行安装逻辑适合批量部署。参数怎么改如果你需要自定义静默参数比如/quiet用${GetOptions}解析命令行或者用FileFunc插件读取$CMDLINE。静默安装的日志建议写到临时目录方便排查部署失败原因。5.4 验证安装器是否“干净”的习惯我每次做完一个自定义安装器都会在干净的虚拟机快照上跑三遍第一遍正常安装检查文件、注册表、开始菜单、控制面板卸载项第二遍卸载检查残留第三遍重装确认没有旧配置干扰。这个流程帮我拦下过好几次“开发机正常、客户机器翻车”的问题。具体检查项用Process Monitor过滤安装器进程看它到底写了哪些注册表键和文件路径有没有写到预期之外的地方。用Dependency Walker确认所有 DLL 依赖都能在目标系统上解析。安装目录用TreeSize看体积是否和预期一致防止误打包了调试符号或缓存文件。从那以后我每次打包安装器都强制走一遍“干净虚拟机三遍验证”不再相信开发机的运行结果。希望帮到你。本文还有配套的精品资源点击获取