ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WiX Toolset 扩展开发入门:从零构建一个可被 candle 与 light 加载的骨架扩展

WiX Toolset 扩展开发入门:从零构建一个可被 candle 与 light 加载的骨架扩展
  • 开发工具
  • 构建工具

【免费下载链接】wix3

WiX Toolset v3.x

项目地址:https://gitcode.com/gh_mirrors/wi/wix3
点击查看免费下载

本文基于 WiX Toolset v3 官方文档《Creating a Simple WiX Extension》,带你完整走通扩展开发的第一步:创建一个继承自WixExtension的骨架扩展程序集,并用-ext开关在 Candle 和 Light 上完成验证。读完本文,你将掌握骨架扩展的 6 个构建步骤、-ext参数在candle.cs/light.cs中的真实解析与加载链路、WixExtension.Load支持的 5 种程序集定位方式,以及仓库内ValidatorExampleExtension这一可对照的完整样例。

一、WiX 扩展是什么,从哪里开始

WiX 扩展用于扩展和定制 WiX 构建的内容与构建的方式。扩展的入口点是一个继承自WixExtension类的 C# 类,它是所有扩展实现(编译器钩子、绑定器钩子、验证器、命令行参数解析等)的容器。

要理解扩展机制,最直接的读物是 WiX 源码中的 WixExtension 基类。从源码结构看,该类全部成员均为virtual且默认返回null——这意味着一个“什么都不做”的空子类也是合法的扩展,这正是骨架扩展能够直接通过 Candle/Light 验证的原因。

二、六步创建骨架 WiX 扩展

以下步骤完整继承自官方文档,并补充了与仓库源码对照的工程细节。

第 1 步:创建 C# 类库项目

在 Visual Studio 中创建一个名为SampleWixExtension的新 C# 类库(.dll)项目。扩展必须是类库而非可执行程序——仓库内的真实扩展工程 ValidatorExampleExtension.csproj 也明确设置了<OutputType>Library</OutputType>。

第 2 步:引用 wix.dll

为项目添加对wix.dll的引用。在仓库源码工程里对应的写法是 ProjectReference:

<Reference Include="System" /> <Reference Include="System.Xml" /> <ProjectReference Include="..\..\..\Wix\Wix.csproj" />

外部开发者则直接引用安装好的 WiX SDK 中的wix.dll即可。

第 3 步:引入命名空间

添加指向核心命名空间的 using 语句:

using Microsoft.Tools.WindowsInstallerXml;

该命名空间是WixExtension与AssemblyDefaultWixExtensionAttribute的所属命名空间,可在 WixExtension.cs 与 AssemblyDefaultWixExtensionAttribute.cs 中确认。

第 4 步:继承 WixExtension

public class SampleWixExtension : WixExtension {}

空类体即可。WixExtension的抽象能力通过虚属性暴露,骨架阶段全部保持默认null实现。

第 5 步:注册默认扩展类型

在项目的AssemblyInfo.cs中添加AssemblyDefaultWixExtensionAttribute:

[assembly: AssemblyDefaultWixExtension(typeof(SampleWixExtension.SampleWixExtension))]

这个属性是命令行“只给 DLL 路径、不指定类名”场景下的关键。从源码看,当WixExtension.Load在程序集中找不到显式类名时,会通过Attribute.GetCustomAttribute读取该属性来获取默认扩展类型;若属性不存在,直接抛出InvalidExtensionType异常(见 WixExtension.cs 第 226-235 行)。该属性本身定义于 AssemblyDefaultWixExtensionAttribute.cs,仅允许作用于程序集([AttributeUsage(AttributeTargets.Assembly)])。仓库内的实例可对照 ValidatorExampleExtension 的 AssemblyInfo.cs:

[assembly: AssemblyDefaultWixExtension(typeof(ValidatorExampleExtension))]

第 6 步:编译

构建项目,得到SampleWixExtension.dll。虽然此时这个扩展尚不会做任何事,但它已经具备了被 WiX 工具链识别和加载的全部要件。

三、用 -ext 验证骨架:candle.exe 与 light.exe

骨架编译完成后,即可将 DLL 通过-ext开关传给 Candle 和 Light 进行编译/链接:

candle.exe Product.wxs -ext SampleWixExtension.dll light.exe Product.wxs -ext SampleWixExtension.dll

这条命令背后的完整调用链可以在仓库源码中逐层验证:

  1. 命令行解析。candle.cs在解析参数时遇到ext会将其取值加入extensionList,若-ext后缺少参数则报告TypeSpecificationForExtensionRequired错误(candle.cs 第 344-355 行);light.cs有完全对称的实现(light.cs 第 581-590 行)。
  2. 扩展加载。Candle 对extensionList中每一项调用WixExtension.Load(extension),随后把同一个实例同时注册给预处理器和编译器:preprocessor.AddExtension(wixExtension)与compiler.AddExtension(wixExtension)(candle.cs 第 149-156 行)。
  3. Light 侧的差异。Light 除了加载扩展外,还会利用扩展的ParseCommandLine消化 light 自身未识别的剩余命令行参数(light.cs 第 142-153 行),并在后续流程中读取扩展提供的本地化资源;此外若扩展提供了CustomBinder,可与命令行-binder指定的类名匹配以替换默认绑定器(light.cs 第 116-121 行)。

只要 candle 与 light 都能正常走完编译与链接流程而没有扩展加载错误,就证明你的骨架扩展在程序集定位、默认扩展类型解析两个环节上均符合要求。

四、WixExtension.Load:-ext 参数到底支持哪些写法

骨架阶段只需给 DLL 路径,但读懂Load的实现有助于排错。WixExtension.Load(WixExtension.cs 第 143-247 行)支持 5 种程序集定位形式:

  1. AssemblyQualifiedName,例如MyExt, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089;
  2. AssemblyName,例如MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089;
  3. 绝对路径,例如C:\MyExtensions\ExtensionAssembly.dll(走Assembly.LoadFrom);
  4. 应用程序目录下的文件名,例如ExtensionAssembly.dll;
  5. 相对路径,例如..\..\MyExtensions\ExtensionAssembly.dll。

另有一条通用规则:可以在程序集名前用逗号前缀显式指定扩展类的全名,如"TopNamespace.ContainingClass, C:\MyExtensions\ExtensionAssembly.dll";未指定类名时则回退到第五步的AssemblyDefaultWixExtensionAttribute。加载成功后,Load会校验目标类型必须IsSubclassOf(typeof(WixExtension)),并用Activator.CreateInstance实例化;任何一步失败都会以WixException抛出InvalidExtension或InvalidExtensionType错误,这正是-ext参数写错时你在命令行看到的错误来源。

五、骨架之后:WixExtension 提供的扩展点

WixExtension通过一组虚属性把不同阶段的钩子暴露给扩展,骨架阶段它们全部返回null,后续按需覆盖即可。整理自 WixExtension.cs 的扩展点如下:

虚成员用途
ParseCommandLine(args, messageHandler)解析扩展自定义的命令行参数,返回未被解析的参数集合供其他扩展继续处理
CompilerExtension提供编译器扩展,在单遍编译的相应时机被调用
BinderExtension提供绑定器扩展
BinderFileManager提供绑定器文件管理器
CustomBinder提供对默认绑定器的完整替换
DecompilerExtension提供反编译器扩展
PreprocessorExtension提供预处理器扩展(自定义指令等)
InspectorExtension提供检查器扩展(Light 的 inspector 阶段)
UnbinderExtension提供解绑器扩展(decompile 场景)
ValidatorExtension提供验证器扩展
TableDefinitions注册扩展自带的内部表定义
DefaultCulture声明扩展本地化的默认语言
GetLibrary(tableDefinitions)返回扩展关联的 WiX 库(wixlib),基类默认返回null

仓库内可运行的完整样例是 ValidatorExampleExtension:它覆盖了InspectorExtension与ValidatorExtension两个属性,并用私有字段对实例做延迟初始化,展示了一个多钩子扩展的典型组织方式——骨架扩展扩展为功能扩展时的形态与它一致。

六、继续深入的两条路径

骨架代码是 WiX 扩展开发的公共基础,完成本文步骤后沿两条主线继续:

  • 构建自定义操作:以骨架代码为起点开发 custom action,然后在 WiX 源码中编写对应操作,参见仓库文档 Adding a Custom Action;
  • 构建预处理器扩展:完整走一遍扩展(含 XSD schema 与表定义)的构建流程,参见 Creating a Preprocessor Extension。

此外,扩展开发的通用约定(目标框架、schema 编写、RequiredVersion属性等)在 Introduction to Developing WiX Extensions 中有系统说明,其中特别提到:多个同类型扩展之间没有处理顺序保证,扩展之间不能存在排序依赖;并建议扩展开发者在<Wix>元素上实现RequiredVersion属性,以便安装项目作者锁定扩展版本。

七、小结

一个可工作的 WiX 骨架扩展只需要四件事:继承WixExtension的空类、对wix.dll的引用、AssemblyDefaultWixExtensionAttribute的程序集级注册、以及一次成功的candle.exe / light.exe -ext验证。理解了WixExtension.Load的 5 种程序集定位规则与默认扩展属性的回退机制,再配合仓库中ValidatorExampleExtension这一参照实现,即可在骨架之上按需覆盖CompilerExtension、ValidatorExtension等虚属性,逐步长出完整的自定义扩展。

  • 开发工具
  • 构建工具

【免费下载链接】wix3

WiX Toolset v3.x

项目地址:https://gitcode.com/gh_mirrors/wi/wix3
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进