简介:这是一份面向Windows桌面开发者的NSIS与Duilib整合实战工程,目标是复刻腾讯QQ安装程序的界面风格与交互流程。资源包共206个文件,约69.53MB,以h头文件、cpp源码、obj编译产物为主,辅以png界面素材、sln与vcxproj工程文件、nsi与nsh脚本,以及7z、rar等第三方依赖压缩包,完整覆盖从源码到构建的各个环节。工程按3party、samples、plugin、include等目录组织,分别承载第三方库、示例模板、NSIS扩展插件与头文件声明,便于理解NSIS脚本与Duilib界面库的协作方式。已有1866人学习下载,适合具备C++基础、希望掌握自定义安装包开发的程序员参考,可从中学习界面渲染、插件调用、脚本编写与安装流程优化的具体实现思路。
1. 从一次打包翻车说起:NSIS+Duilib 仿 QQ 安装包到底能解决什么
上周帮朋友处理一个桌面工具的发布,用某商业打包工具生成的安装包,界面是默认的灰底下一步下一步,用户装完就忘。更头疼的是,安装过程中想加一个自定义的协议注册和静默参数判断,翻遍了文档也没找到干净的入口。后来换回 NSIS 配合 Duilib 重写界面,才算把安装流程的控制权拿回来。这套 NSIS+Duilib 仿 QQ 安装包源码,核心就是用 NSIS 做安装逻辑引擎,用 Duilib 做界面渲染层,复刻出类似 QQ 安装包那种带自定义背景、动态按钮、协议勾选、路径选择的安装体验。它适合两类人:一是需要给桌面软件做品牌化安装界面的开发者,二是想学 NSIS 插件通信和 Duilib 窗口嵌入的 Windows 客户端工程师。如果你正在找一套能直接跑起来、又能拆开改的安装包模板,这份资源值得花时间拆一遍。
2. NSIS 与 Duilib 的协作机制:为什么不是单纯换皮
2.1 NSIS 负责什么,Duilib 负责什么
NSIS 本身是一个脚本驱动的安装制作工具,它的强项在于文件释放、注册表写入、快捷方式创建、卸载信息登记这些安装器该干的脏活累活。但它的原生界面能力停留在 Win32 控件级别,按钮样式、背景图、动画效果都很难做到现代安装包那种视觉水准。Duilib 则是一个轻量级 Windows 界面库,基于 DirectUI 思想,把控件绘制、消息路由、布局管理都抽象出来,适合做无边框、自绘风格的窗口。
这套源码的分工很明确:NSIS 脚本负责安装流程的调度,包括页面跳转、文件解压、注册表操作、静默参数解析;Duilib 编译出的 DLL 负责界面渲染和用户交互,包括背景绘制、按钮悬停效果、协议勾选框、路径编辑框。两者通过 NSIS 的插件接口通信,NSIS 在合适的时机调用 Duilib 插件,Duilib 把用户操作结果回传给 NSIS。
常见做法是,NSIS 脚本里用nsisDuilib::showPage这类自定义插件函数触发界面显示,Duilib 内部维护一个状态机,把安装路径、协议勾选状态、是否创建桌面快捷方式这些数据通过共享内存或窗口消息回传。这样 NSIS 不需要关心界面怎么画,Duilib 也不需要关心文件怎么释放。
2.2 为什么不用 NSIS 原生界面加皮肤
有人会问,NSIS 不是有 MUI2 吗,换套皮肤不就行了。实际用过就知道,MUI2 的皮肤机制本质还是 Win32 控件换位图,按钮的悬停、按下、禁用三态切换很生硬,背景图拉伸容易糊,高 DPI 下更是灾难。Duilib 的优势在于它自己接管了绘制,所有控件都是自绘的,你可以精确控制每个像素的渲染,还能做半透明、圆角、渐变这些效果。仿 QQ 安装包那种左侧大图、右侧选项、底部按钮栏的布局,用 Duilib 做起来比 MUI2 顺手得多。
另一个原因是交互逻辑的复杂度。QQ 安装包里有安装路径的实时校验、磁盘空间检测、协议勾选状态联动安装按钮的可用性,这些用 NSIS 原生控件写起来很别扭,而 Duilib 里就是几个控件属性和消息处理的事。
2.3 插件通信的基本约定
NSIS 调用 Duilib 插件,本质上是通过pluginapi.h里的导出函数。Duilib 编译成 DLL 后,需要导出NSISPluginInit、NSISPluginUnload以及具体的页面显示函数。NSIS 脚本里用nsisDuilib::函数名的方式调用,参数通过 NSIS 的栈传递。
一个典型的调用约定是这样的:
; NSIS 脚本中调用 Duilib 插件显示安装选项页 nsisDuilib::ShowInstallPage /NOUNLOAD "$INSTDIR" Pop $0 ; 返回值,0 表示用户点击了安装,1 表示取消Duilib 侧接收参数后,把安装路径设置到界面控件上,然后进入消息循环。用户点击安装按钮时,Duilib 把当前界面上的路径、勾选状态打包,通过push指令压回 NSIS 栈,然后返回。NSIS 拿到返回值后决定下一步是执行安装还是回退页面。
注意:NSIS 插件调用是同步的,Duilib 的消息循环必须在插件函数返回前结束,否则 NSIS 主线程会卡死。常见做法是在 Duilib 里用模态对话框的方式跑消息循环,收到确定或取消后退出循环。
3. 把源码跑起来:编译环境、目录结构与关键配置
3.1 编译环境准备
这套源码涉及两个编译产物:Duilib 的 DLL 和 NSIS 的安装包。Duilib 部分用 Visual Studio 编译,建议 VS2015 及以上,平台工具集选 v140 或 v141,字符集用 Unicode。NSIS 部分需要安装 NSIS 3.x 版本,并且把 Duilib 编译出的 DLL 放到 NSIS 的插件目录或者安装包脚本同级目录。
依赖项方面,Duilib 本身依赖 Windows SDK 和 DirectX SDK 的一部分头文件,但源码包里通常已经包含了精简版的 Duilib,不需要额外装 DirectX SDK。如果编译时报d3d9.h找不到,检查 VS 安装时有没有勾选 Windows SDK 的图形组件。
3.2 目录结构说明
源码包的目录结构大致如下,不同版本可能略有差异:
| 目录/文件 | 作用 |
|---|---|
Duilib/ | Duilib 库源码,包含核心绘制和控件实现 |
InstallerUI/ | 安装包界面工程,编译出InstallerUI.dll |
NSISScript/ | NSIS 脚本目录,含主脚本和页面逻辑 |
NSISScript/Plugins/ | 存放编译好的 Duilib 插件 DLL |
Resource/ | 界面用到的图片、XML 布局文件 |
Build/ | 编译输出目录,含最终安装包 exe |
InstallerUI工程里会有一个stdafx.h和InstallerUI.cpp,入口函数是NSISPluginInit。界面布局用 Duilib 的 XML 描述,放在Resource目录下,运行时通过相对路径加载。
3.3 编译 Duilib 插件
打开InstallerUI.sln,选择 Release 配置,平台选 Win32 或 x64 要和 NSIS 脚本里的目标架构一致。编译前检查几个关键配置:
// InstallerUI.cpp 中插件导出函数示例 extern "C" __declspec(dllexport) void NSISPluginInit(HWND hParent, int code, char* szCmdLine) { // hParent 是 NSIS 传进来的父窗口句柄 // code 是调用码,区分不同页面 // szCmdLine 是命令行参数,通常传安装路径 if (code == 1) { // 显示安装选项页 CInstallWnd wnd; wnd.Create(hParent, _T("安装选项"), UI_WNDSTYLE_DIALOG, 0); wnd.CenterWindow(); wnd.ShowModal(); } }编译成功后,把InstallerUI.dll复制到NSISScript/Plugins/目录下。如果 NSIS 脚本里用的是nsisDuilib::前缀,DLL 文件名需要和脚本里的插件名一致,或者用!addplugindir指定插件搜索路径。
3.4 NSIS 脚本关键段落实例
主脚本里控制页面跳转和安装逻辑的部分大概长这样:
; 定义安装目录变量 Var InstallPath Var ProtocolChecked ; 页面:欢迎页之后调用 Duilib 显示安装选项 Function ShowOptionsPage ; 从注册表或默认路径读取初始安装目录 StrCpy $InstallPath "$PROGRAMFILES\MyApp" ; 调用 Duilib 插件,传入初始路径 nsisDuilib::ShowInstallPage /NOUNLOAD "$InstallPath" Pop $0 ; 返回值:0=安装,1=取消 ; 从插件获取用户修改后的路径和协议勾选状态 nsisDuilib::GetInstallPath /NOUNLOAD Pop $InstallPath nsisDuilib::GetProtocolChecked /NOUNLOAD Pop $ProtocolChecked StrCmp $0 "0" +2 Abort ; 用户取消,终止安装 FunctionEnd这段脚本的逻辑是:先给一个默认安装路径,调用 Duilib 界面让用户确认或修改,用户点击安装后从插件取回最终路径和协议勾选状态,如果用户点了取消就Abort终止安装流程。参数/NOUNLOAD表示插件调用后不卸载 DLL,因为后面还要继续调用GetInstallPath和GetProtocolChecked。
提示:如果 NSIS 脚本里调用插件时报「插件未找到」,检查 DLL 是否放到了
NSISScript/Plugins/下,或者脚本开头有没有用!addplugindir指定正确路径。32 位 NSIS 只能加载 32 位 DLL,64 位同理。
4. 避坑与排查:五个让安装包翻车的细节
4.1 现象:安装界面显示空白或花屏
原因:Duilib 加载 XML 布局文件时路径不对,或者图片资源没有正确打包进安装包。NSIS 编译时如果没把Resource目录下的图片和 XML 一起释放到临时目录,Duilib 运行时找不到文件就会绘制异常。
解决:在 NSIS 脚本的Section里先把资源文件释放到$PLUGINSDIR或$TEMP下的一个子目录,然后通过命令行参数把资源路径传给 Duilib 插件。Duilib 侧用绝对路径加载 XML,不要用相对路径。
4.2 现象:点击安装按钮没反应,界面卡死
原因:Duilib 的消息循环没有正确退出,或者 NSIS 插件调用时父窗口句柄传错了。常见情况是 Duilib 窗口创建时用了HWND_DESKTOP而不是 NSIS 传进来的hParent,导致窗口不在 NSIS 的消息队列里。
解决:检查Create函数的第一个参数,确保用的是NSISPluginInit回调里拿到的hParent。另外确认ShowModal内部的消息循环在收到WM_CLOSE或自定义退出消息时能正确PostQuitMessage。
4.3 现象:高 DPI 显示器上界面模糊或错位
原因:Duilib 默认按 96 DPI 布局,系统缩放 125% 或 150% 时,窗口按物理像素创建,但控件坐标还是逻辑像素,导致错位。NSIS 本身对高 DPI 的支持也有限。
解决:在 Duilib 窗口创建前调用SetProcessDPIAware,然后根据GetDeviceCaps(LOGPIXELSX)获取实际 DPI,对布局坐标做等比缩放。或者直接在清单文件里声明 DPI 感知,让系统不做虚拟化。仿 QQ 安装包这类界面,建议按 96 DPI 设计,运行时统一缩放。
4.4 现象:静默安装参数失效
原因:NSIS 脚本里判断/S参数的逻辑放在了 Duilib 页面调用之后,导致静默模式下还是弹出了界面。或者 Duilib 插件在静默模式下没有正确跳过界面显示。
解决:在.onInit回调里最先判断IfSilent,静默模式下直接走安装逻辑,不调用任何界面插件。Duilib 侧也要加一个判断,如果收到静默标志就立即返回默认值,不创建窗口。
4.5 现象:卸载时残留文件或注册表项
原因:NSIS 的卸载段没有清理 Duilib 释放到临时目录的资源文件,或者注册表写入时用了WriteRegStr但卸载时忘了DeleteRegKey。
解决:在Section Uninstall里显式删除$PLUGINSDIR和$TEMP下的资源目录,注册表项用DeleteRegKey HKLM "Software\MyApp"整树删除。如果安装时注册了文件关联或协议,卸载时也要对应清理。
5. 进阶技巧:让安装包更像一个产品
5.1 用 XML 布局做多语言切换
Duilib 的界面布局完全由 XML 描述,这意味着你可以准备多套 XML 文件,运行时根据系统语言或命令行参数加载不同的 XML。比如install_zh.xml和install_en.xml,NSIS 脚本里通过$LANGUAGE变量判断当前语言,把对应文件名传给 Duilib 插件。这样不需要重新编译 DLL,只替换资源文件就能切换界面语言。
; 根据系统语言选择 XML 文件 ${If} $LANGUAGE == 2052 StrCpy $R0 "install_zh.xml" ${Else} StrCpy $R0 "install_en.xml" ${EndIf} nsisDuilib::ShowInstallPage /NOUNLOAD "$InstallPath" "$R0"参数说明:$LANGUAGE是 NSIS 内置变量,2052 对应简体中文。$R0是临时寄存器,用来存 XML 文件名。调用插件时把文件名作为第二个参数传入,Duilib 侧根据文件名加载对应布局。
5.2 安装进度与后台解压的同步
仿 QQ 安装包通常有一个进度条页面,展示文件解压进度。NSIS 的File指令是同步的,解压大文件时界面会卡住。常见做法是把解压操作放到一个单独的线程里,Duilib 界面通过定时器查询进度并刷新进度条。NSIS 侧可以用nsisDuilib::SetProgress插件函数,在解压循环里不断更新进度值。
; 分块解压并更新进度 StrCpy $R1 0 ${For} $R2 1 10 ; 每次解压一部分文件 File "/oname=$INSTDIR\part$R2.dat" "data\part$R2.dat" IntOp $R1 $R1 + 10 nsisDuilib::SetProgress /NOUNLOAD "$R1" ${Next}这段脚本把解压分成 10 个批次,每完成一批就调用插件更新进度条。实际项目中批次划分要按文件大小来,避免某个批次过大导致进度条跳跃。
5.3 验证安装包完整性的一个习惯
从那以后我每次编译完安装包,都会在虚拟机里跑一遍完整流程:全新安装、覆盖安装、静默安装、卸载、再次安装。重点看三个地方:安装目录下文件是否齐全、注册表项是否写入正确、控制面板里卸载信息是否正常显示。这套 NSIS+Duilib 仿 QQ 安装包源码的价值不在于界面多好看,而在于它把安装器该有的控制权都暴露出来了,你可以精确控制每一步的行为。希望帮到你。
本文还有配套的精品资源,点击获取