- 开发工具
- 构建工具
【免费下载链接】wix3
WiX Toolset v3.x
Burn 是 WiX Toolset v3.x 中负责下载、缓存与链式安装多个安装包的引导引擎(bootstrapper/chainer),它以可执行程序的形式承载一个被称为"引导程序应用"(Bootstrapper Application,简称 BA)的 DLL。本文基于仓库文档 bootstrapper_application_interface.html.md 与 Building a Custom Bootstrapper Application,系统讲解引擎与 BA 之间通过IBootstrapperApplication(引擎回调 BA)与IBootstrapperEngine(BA 指挥引擎)两个 COM 接口建立的双向协作模型,完整覆盖从OnStartup启动、Detect检测、Plan计划、Apply应用到Shutdown/OnShutdown收尾的完整生命周期,并结合仓库源码(IBootstrapperApplication.h、IBootstrapperEngine.h、engine.cpp 等)揭示底层消息机制与 DLL 加载细节。读完本文,你将能够理解自定义 BA 的接口契约、回调时序与返回值语义,并掌握在 Bundle 中装配标准或自定义 BA 的完整方法。
一、协作模型:引擎与引导程序应用的两个方向
在 WiX 的 Burn 架构中,职责被清晰地切分为两半:
- Burn 引擎(engine):一个原生可执行程序,负责解析 Bundle 清单、缓存负载(payload)、执行 MSI/MSP/EXE 等包的安装与卸载,并维护日志与回滚状态。仓库中引擎实现位于 src/burn/engine。
- 引导程序应用(BA):一个 DLL,负责面向最终用户的一切交互——显示 UI、收集安装位置与功能选择、决定何时下载/安装/修复/卸载,以及把用户的决定翻译成对引擎的指令。
两者之间通过两个 COM 接口通信:
| 接口 | 方向 | 定义位置 | 说明 |
|---|---|---|---|
IBootstrapperApplication | 引擎 → BA(回调) | IBootstrapperApplication.h,IID53C31D56-49C0-426B-AB06-099D717C67FE | 引擎主动向 BA 报告检测、计划、应用各阶段的事件与进度,BA 通过返回值影响引擎行为 |
IBootstrapperEngine | BA → 引擎(命令) | IBootstrapperEngine.h,IID6480D616-27A0-44D7-905B-81512C29C2FB | BA 向引擎下达Detect、Plan、Apply、Quit等命令,并读写变量、格式化字符串 |
正如文档所述:"The engine communicates with the bootstrapper application through callbacks to the IBootstrapperApplication interface"——引擎发给 BA 的第一条消息就是IBootstrapperApplication::OnStartup()。
二、起点:OnStartup 与引擎的消息循环
引擎加载 BA 后发起的第一个回调是OnStartup:
// IBootstrapperApplication::OnStartup STDMETHOD(OnStartup)() = 0;典型的 BA 会在这个回调中启动一个新线程并显示用户界面,然后立即返回。文档特别强调:"After the BA returns from OnStartup, the engine enters its idle loop and waits for commands from the BA via IBootstrapperEngine."——即 BA 从OnStartup返回后,引擎就进入空闲循环(idle loop),不再主动推进任何安装逻辑,接下来的一切都由 BA 通过IBootstrapperEngine发命令驱动。
从源码结构看,这一流程在 engine.cpp 中对应引擎的主运行函数:先通过EngineForApplicationCreate创建供 BA 使用的引擎接口对象,再调用UserExperienceLoad加载 BA DLL 并取得其实例,随后调用OnStartup(源码第 711 行pEngineState->userExperience.pUserExperience->OnStartup()),紧接着进入一个标准的 Windows 消息泵:
while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0))) { if (-1 == fRet) { ... } else { ProcessMessage(pEngineState, &msg); } }这个细节解释了 Burn 的一条核心设计原则:引擎与 BA 之间的所有命令都是异步投递的。BA 调用IBootstrapperEngine的方法并不会同步执行安装逻辑,而是把对应的窗口消息(如WM_BURN_DETECT、WM_BURN_PLAN、WM_BURN_APPLY、WM_BURN_QUIT)投递到引擎线程的消息队列,由引擎的消息泵逐个处理。实现证据见 EngineForApplication.cpp 与 engine.cpp 中的ProcessMessage分发逻辑。
三、检测阶段:IBootstrapperEngine::Detect
BA 启动后的第一件事应该是检测——即让引擎扫描目标机器,判断链中各包当前的安装状态。BA 通过调用IBootstrapperEngine::Detect发起:
// IBootstrapperEngine::Detect STDMETHOD(Detect)() = 0;在当前的仓库源码中,Detect还带有一个可选的父窗口句柄参数(见 IBootstrapperEngine.h):
STDMETHOD(Detect)( __in_opt HWND hwndParent = NULL ) = 0;调用后引擎异步执行检测,并通过IBootstrapperApplication回调把过程与结果汇报给 BA。与检测相关的回调在头文件中有完整的定义,主要包括:
OnDetectBegin(fInstalled, cPackages):检测开始,fInstalled表示 Bundle 当前是否已安装,cPackages为待检测包数量;OnDetectForwardCompatibleBundle:检测到前向兼容的 Bundle(可用于替换);OnDetectUpdateBegin/OnDetectUpdate/OnDetectUpdateComplete:Bundle 自更新候选的检测;OnDetectRelatedBundle:检测到相关 Bundle(升级、补丁、依赖等关系);OnDetectPackageBegin/OnDetectCompatiblePackage/OnDetectRelatedMsiPackage/OnDetectTargetMsiPackage/OnDetectMsiFeature:针对单个包的检测细节;OnDetectPackageComplete(wzPackageId, hrStatus, state)/OnDetectComplete(hrStatus):单个包与整个检测阶段的结束信号,state为BOOTSTRAPPER_PACKAGE_STATE枚举(UNKNOWN/ABSENT/CACHED/PRESENT/SUPERSEDED等)。
值得注意的返回值约定(同样适用于后文大部分回调):回调返回IDCANCEL可中止当前阶段,返回IDNOACTION则继续。这两个常量及IDDOWNLOAD(101)、IDRESTART(102)、IDSUSPEND(103)、IDRELOAD_BOOTSTRAPPER(104) 都在 IBootstrapperEngine.h 中定义,IDERROR为 -1,IDNOACTION为 0。
四、计划阶段:IBootstrapperEngine::Plan 与 BOOTSTRAPPER_ACTION
检测完成后,BA 需要确定用户想要执行的总体操作。文档指出:"Historically this happens as a wizard sequence, prompting the user for installation location, feature selection, etc."——典型的 BA 会以向导(wizard)形式逐步询问安装位置、功能选择等,待所有决定就绪后,调用Plan让引擎制定执行计划:
// IBootstrapperEngine::Plan STDMETHOD(Plan)( __in BOOTSTRAPPER_ACTION action ) = 0;BOOTSTRAPPER_ACTION是一个枚举,用于指定总体动作。文档强调最常用的动作是install(安装)、uninstall(卸载)和 repair(修复)。当前仓库头文件中的完整枚举(顺序敏感,源码注释明确指出枚举值的排列顺序不可随意改动,因为部分代码路径依赖</>比较):
| 枚举值 | 含义 |
|---|---|
BOOTSTRAPPER_ACTION_UNKNOWN | 未知动作 |
BOOTSTRAPPER_ACTION_HELP | 显示帮助 |
BOOTSTRAPPER_ACTION_LAYOUT | 仅布局(下载/缓存负载而不安装) |
BOOTSTRAPPER_ACTION_UNINSTALL | 卸载 |
BOOTSTRAPPER_ACTION_CACHE | 仅缓存 |
BOOTSTRAPPER_ACTION_INSTALL | 安装 |
BOOTSTRAPPER_ACTION_MODIFY | 修改 |
BOOTSTRAPPER_ACTION_REPAIR | 修复 |
BOOTSTRAPPER_ACTION_UPDATE_REPLACE | 以更新包替换当前 Bundle |
BOOTSTRAPPER_ACTION_UPDATE_REPLACE_EMBEDDED | 以内嵌更新包替换当前 Bundle |
计划阶段的回调包括OnPlanBegin(cPackages)、OnPlanRelatedBundle、OnPlanPackageBegin(wzPackageId, pRequestedState)、OnPlanCompatiblePackage、OnPlanTargetMsiPackage、OnPlanMsiFeature,以及结束信号OnPlanPackageComplete和OnPlanComplete(hrStatus)。注意多个"Plan"回调都带有__inout BOOTSTRAPPER_REQUEST_STATE* pRequestedState参数——BA 不仅被动接收计划状态,还可以就地改写请求状态(FORCE_ABSENT/ABSENT/CACHE/PRESENT/REPAIR),从而影响该包最终是安装、卸载、仅缓存还是修复,这是实现复杂交互逻辑的关键挂点。
五、应用阶段:IBootstrapperEngine::Apply
计划完成之后,BA 调用Apply让引擎真正执行变更:
// IBootstrapperEngine::Apply STDMETHOD(Apply)( __in_opt HWND hwndParent ) = 0;文档特别强调了hwndParent的作用:BA 应提供一个窗口句柄,以确保需要提权(elevation)时出现的 UAC 提示能够处于活动状态并显示在其他窗口之上。若 BA 是"被动式"(passive)或嵌入式场景,可传入NULL。仓库中Apply同样以WM_BURN_APPLY消息异步投递(见 EngineForApplication.cpp)。
文档还指出:"The bulk of the BA time will be spent handling callbacks from the Apply action."——BA 的绝大部分运行时间都花在处理 Apply 阶段的各种回调上。Apply 是一个多阶段的流水线,IBootstrapperApplication头文件中按顺序定义了完整的回调集,可归纳为:
- 开始/结束:
OnApplyBegin、OnApplyPhaseCount(dwPhaseCount)(v3 中紧随OnApplyBegin之后,告知 BA 阶段总数)、OnApplyComplete(hrStatus, restart); - 提权:
OnElevate(每次引擎执行仅触发一次,返回IDCANCEL可中止提权并停止应用); - 进度:
OnProgress(dwProgressPercentage, dwOverallPercentage); - 错误:
OnError(errorType, wzPackageId, dwCode, wzError, uiFlags, cData, rgwzData, nRecommendation),errorType为BOOTSTRAPPER_ERROR_TYPE(ELEVATE/WINDOWS_INSTALLER/EXE_PACKAGE/HTTP_AUTH_SERVER/HTTP_AUTH_PROXY/APPLY),返回IDNOACTION会让引擎走默认错误处理(通常导致 Apply 失败); - 注册:
OnRegisterBegin/OnRegisterComplete(hrStatus),以及卸载路径上的OnUnregisterBegin/OnUnregisterComplete; - 缓存(Cache):
OnCacheBegin、OnCachePackageBegin、OnCacheAcquireBegin/Progress/Complete、OnCacheVerifyBegin/Complete、OnCachePackageComplete、OnCacheComplete;其中OnResolveSource用于本地找不到负载时决定"重试本地源(IDRETRY)"还是"改用下载源(IDDOWNLOAD)",BA 可在返回前调用IBootstrapperEngine::SetLocalSource/SetDownloadSource更换来源; - 执行(Execute):
OnExecuteBegin、OnExecutePackageBegin(wzPackageId, fExecute)、OnExecutePatchTarget、OnExecuteProgress、OnExecuteMsiMessage、OnExecuteFilesInUse、OnExecutePackageComplete、OnExecuteComplete; - 预批准程序:
OnLaunchApprovedExeBegin/OnLaunchApprovedExeComplete(hrStatus, dwProcessId)。
OnExecutePackageComplete的返回值值得单独说明,因为它是实现"安装后重启"的关键:IDRESTART指示引擎停止处理链并重启机器(引擎在重启后会再次启动继续);IDSUSPEND指示引擎挂起当前状态(可用于配合后续恢复);IDIGNORE/IDRETRY则分别表示忽略非关键包的失败或重试该包。对应地,OnApplyComplete返回IDRESTART也可请求整体重启(若已由OnExecutePackageComplete发起过重启则被忽略)。
六、收尾:通知引擎退出与 OnShutdown
当 BA 完成所有工作(或用户取消/出错)时,它应通知引擎退出。文档中给出的调用是:
// IBootstrapperEngine::Shutdown STDMETHOD(Shutdown)( __in DWORD dwExitCode, __in BOOL fRestart ) = 0;需要提醒的是:当前仓库的实际头文件 IBootstrapperEngine.h 中,这一方法名为Quit,且只接受退出码:
STDMETHOD(Quit)( __in DWORD dwExitCode ) = 0;从源码结构看,Quit通过PostThreadMessageW(m_dwThreadId, WM_BURN_QUIT, dwExitCode, 0)投递消息,引擎消息泵收到WM_BURN_QUIT后调用CoreQuit并退出消息循环(见 engine.cpp)。文档中的fRestart参数(是否需要重启)在 v3 源码中实际上由OnShutdown的返回值承担。因此编写 BA 时应以当前仓库头文件签名为准,文档所述属于较早版本的接口形态。
BA 发起退出后,引擎会最后一次回调 BA 的OnShutdown:
// IBootstrapperApplication::OnShutdown STDMETHOD_(void, OnShutdown)() = 0;同样地,当前仓库头文件 IBootstrapperApplication.h 中OnShutdown已升级为返回int的形态,可借此向引擎传达特殊指令:
- 返回
IDRESTART:指示引擎重启机器(引擎在重启后不再自动重新启动;若OnExecutePackageComplete已发起过重启则忽略); - 返回
IDRELOAD_BOOTSTRAPPER:指示引擎卸载 BA 并重新加载引擎、再次加载 BA,典型用途是从原生 BA 切换到托管(managed)BA; - 返回其他值:一律忽略。
引擎退出路径的处理位于 engine.cpp:退出消息循环后调用OnShutdown,根据返回值设置fRestart或pfReloadApp,随后卸载 UX(UserExperienceUnload)并释放引擎接口对象。
七、引擎如何装载 BA:导出函数与 BOOTSTRAPPER_COMMAND
BA 之所以是一个 DLL,是因为引擎通过标准的 DLL 加载机制与其对接。加载流程实现在 userexperience.cpp 的UserExperienceLoad中:先LoadLibraryExW加载 BA DLL(以LOAD_WITH_ALTERED_SEARCH_PATH方式保证能找到随附的资源 payload),再用GetProcAddress查找导出函数BootstrapperApplicationCreate,最后调用它创建 BA 实例。
该导出函数的签名(与 ba/index.html.md 一致):
extern "C" HRESULT WINAPI BootstrapperApplicationCreate( __in IBootstrapperEngine* pEngine, __in const BOOTSTRAPPER_COMMAND* pCommand, __out IBootstrapperApplication** ppApplication )pEngine:引擎提供的IBootstrapperEngine接口指针,BA 保存它即可在后续任意时刻向引擎发命令;pCommand:指向BOOTSTRAPPER_COMMAND结构,内含从命令行解析出的信息。该结构在 IBootstrapperApplication.h 中定义,字段包括action(期望动作)、display(BOOTSTRAPPER_DISPLAY:EMBEDDED/NONE/PASSIVE/FULL)、restart(BOOTSTRAPPER_RESTART:NEVER/PROMPT/AUTOMATIC/ALWAYS)、wzCommandLine、nCmdShow、resumeType(BOOTSTRAPPER_RESUME_TYPE,如REBOOT、INTERRUPTED、ARP等,用于实现断点恢复)、hwndSplashScreen、relationType、fPassthrough、wzLayoutDirectory;ppApplication:成功时返回 BA 的IBootstrapperApplication实现。
BA 还可可选地导出BootstrapperApplicationDestroy,引擎会在卸载 DLL 之前调用它:
extern "C" void WINAPI BootstrapperApplicationDestroy()文档指出:绝大多数清理工作应在IBootstrapperApplication::OnShutdown中完成,BootstrapperApplicationDestroy只用于清理那些在BootstrapperApplicationCreate期间创建、需要与 BA 实例同生共死的资源。引擎侧在UserExperienceUnload中通过GetProcAddress("BootstrapperApplicationDestroy")探测并调用该导出(见 userexperience.cpp),随后FreeLibrary。
兼容性警示(引自 ba/index.html.md):升级 WiX Toolset 的 minor 版本时必须重新编译 BA——minor 版本只保证源代码级兼容,不保证二进制兼容。这是自定义 BA 开发者需要长期遵守的发布纪律。
八、命令投递的底层机制:线程消息
把前面各节串起来看,Burn 的异步协作机制可以归纳为一条完整的调用链:
- BA 调用
IBootstrapperEngine::Detect/Plan/Apply/Quit; - 引擎接口实现(EngineForApplication.cpp)把这些调用转换为
PostThreadMessageW投递的WM_BURN_DETECT/WM_BURN_PLAN/WM_BURN_APPLY/WM_BURN_QUIT消息; - 引擎线程的消息泵(engine.cpp 的
ProcessMessage)取出消息,分发到CoreDetect/CorePlan/CoreApply/CoreQuit等核心函数; - 核心函数执行期间通过
IBootstrapperApplication回调把阶段事件、进度和错误反馈给 BA。
因此,BA 中所有引擎调用都会立即返回(只保证"投递成功"),真正的安装动作发生在引擎线程上。这也是为什么 BA 的 UI 线程可以保持响应、进度条可以平滑更新——UI 渲染与安装执行天然解耦。如果 BA 需要在引擎繁忙时保护状态,可参考引擎侧提供的UserExperienceActivateEngine/UserExperienceDeactivateEngine/UserExperienceEnsureEngineInactive等同步辅助函数(见 userexperience.cpp),它们通过临界区保证同一时刻只有一个方向在操作引擎。
九、在 Bundle 中装配 BA:BootstrapperApplication 与 WixStandardBootstrapperApplication
理解接口之后,回到 WiX 语言层面。文档 authoring_bundle_application.html.md 说明:每个 Bundle 都需要一个 BA 来驱动 Burn 引擎。
<BootstrapperApplication>元素用于定义一个全新的 BA;<BootstrapperApplicationRef>元素用于引用已存在于某个<Fragment>或 WiX 扩展中的 BA。
绝大多数场景无需编写自定义 BA,因为 WiX 提供了标准 BA(WiX Standard Bootstrapper Application,位于 WixBalExtension.dll)。在 Bundle 中引用它:
<?xml version="1.0"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> <Bundle> <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" /> <Chain> </Chain> </Bundle> </Wix>标准 BA 提供多个变体(详见 wixstdba/index.html.md):
WixStandardBootstrapperApplication.RtfLicense——欢迎页显示 RTF 许可证(类似 WixUI Advanced);WixStandardBootstrapperApplication.HyperlinkLicense——欢迎页以超链接方式提供许可证,观感更现代简洁;WixStandardBootstrapperApplication.HyperlinkSidebarLicense——基于 HyperlinkLicense,但对话框更大、首页图片更大;WixStandardBootstrapperApplication.RtfLargeLicense——类似 RtfLicense 的大对话框版本,可显示版本号;WixStandardBootstrapperApplication.HyperlinkLargeLicense——类似 HyperlinkLicense 的大对话框版本,可显示版本号。
后三种变体可通过bal:WixStandardBootstrapperApplication子元素打开ShowVersion="yes"在欢迎页显示 Bundle 版本:
<?xml version="1.0"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"> <Bundle> <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense"> <bal:WixStandardBootstrapperApplication LicenseFile="path\to\license.rtf" ShowVersion="yes" /> </BootstrapperApplicationRef> <Chain> </Chain> </Bundle> </Wix>构建时须提供 WixBalExtension。若上述代码存于example.wxs,则依次执行即可产出example.exeBundle:
candle.exe example.wxs -ext WixBalExtension light.exe example.wixobj -ext WixBalExtension当标准 BA 无法满足需求时,可开发自定义 BA DLL 并用<BootstrapperApplication>装配:
<?xml version="1.0"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> <Bundle> <BootstrapperApplication SourceFile="path\to\ba.dll" /> <Chain> </Chain> </Bundle> </Wix>自定义 BA 往往需要随附资源文件(本地化资源、主题等),可在<BootstrapperApplication>内部添加<Payload>子元素,或通过<PayloadGroupRef>引用其他<Fragment>中定义的<PayloadGroup>:
<?xml version="1.0"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> <Bundle> <BootstrapperApplication SourceFile="path\to\ba.dll"> <Payload SourceFile="path\to\en-us\resources.dll" /> <PayloadGroupRef Id="ResourceGroupforJapanese" /> </BootstrapperApplication> <Chain> </Chain> </Bundle> </Wix>注意 BA DLL 及其全部 payload 都会被引擎当作 UX 负载缓存到临时目录,且第一个 payload 即 BA DLL 本身(见 userexperience.cpp 中payloads.rgPayloads[0]的加载逻辑),其余 payload 会被放置在同一工作目录下供LoadLibraryExW(LOAD_WITH_ALTERED_SEARCH_PATH)解析依赖。
十、实战要点速查
- 生命周期铁律:
OnStartup中启动 UI 线程后立即返回 → 引擎进入消息循环 → BA 用Detect开始 → 用Plan(BOOTSTRAPPER_ACTION)制定计划 → 用Apply(hwndParent)执行 → 用Quit/Shutdown(dwExitCode)退出 → 引擎最后回调OnShutdown。 - 同步性是假象:所有引擎命令均为异步投递(线程消息),立即返回只代表投递成功;真实进度通过回调反映,BA 的 UI 不应假设命令已执行完毕。
- 回调返回值是控制手段:绝大多数回调返回
IDCANCEL中止、IDNOACTION继续;OnResolveSource还接受IDRETRY/IDDOWNLOAD;OnExecutePackageComplete/OnApplyComplete接受IDRESTART/IDSUSPEND等;OnShutdown接受IDRESTART/IDRELOAD_BOOTSTRAPPER。 Plan类回调可改写请求状态:通过修改pRequestedState就地决定包的安装/卸载/修复/缓存,是自定义交互逻辑的主要扩展点。- 必须提供
hwndParent给Apply,否则提权提示可能无法正确置顶显示。 - 导出两个函数:
BootstrapperApplicationCreate(必选)与BootstrapperApplicationDestroy(可选),签名以 IBootstrapperApplication.h 为准。 - 保持二进制兼容:WiX minor 版本升级需重编译 BA。
- 不想写 C++ 也可用托管方案:参考 ManagedBundleRunner 等示例,以及仓库内置的 WixBA(src/Setup/WixBA)——后者本身就是一份完整的原生 BA 参考实现,包含根视图、安装/进度/更新视图模型(RootViewModel.cs、InstallationViewModel.cs 等),可作为研读接口用法的活教材。
若需继续深入,仓库内与本文配套的文档还包括 Building Installation Package Bundles、Author the Bootstrapper Application for a Bundle、Author a Bundle Package Manifest 与 Working with WiX Standard Bootstrapper Application,可沿此路径系统掌握 Bundle 构建的完整知识体系。
- 开发工具
- 构建工具
【免费下载链接】wix3
WiX Toolset v3.x
相关推荐
WiX Toolset v3 项目教程
WiX Toolset v3 项目教程 1. 项目的目录结构及介绍 WiX Toolset v3 项目的目录结构如下: wix3/ ├── CONTRIBUTI
开发工具构建工具【亲测免费】 WiX Toolset v3 使用教程
WiX Toolset v3 使用教程 1. 项目介绍 WiX Toolset 是一个用于构建 Windows 安装包的开源工具集。它允许开发者通过 XML 源
开发工具构建工具WiX Toolset v3: 打造专业Windows安装程序的利器
WiX Toolset v3: 打造专业Windows安装程序的利器 WiX Toolset v3,一个专为Windows安装包构建而生的开源工具集,采用XML
开发工具构建工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考