编译 WPF 应用程序
Windows Presentation Foundation (WPF) 应用程序可以生成为 .NET Framework 可执行文件(.exe)、库(.dll),或这两种类型的程序集的组合。 本主题介绍如何生成 WPF 应用程序,并介绍生成过程中的关键步骤。
生成 WPF 应用程序
可以通过以下方式编译 WPF 应用程序:
命令行。 应用程序必须仅包含代码(无 XAML)和应用程序定义文件。 有关详细信息,请参阅 命令行生成与 csc.exe 或 从命令行(Visual Basic)生成。
Microsoft生成引擎(MSBuild)。 除了代码和 XAML 文件,应用程序还必须包含 MSBuild 项目文件。 有关详细信息,请参阅“MSBuild”。
Visual Studio。 Visual Studio 是一种集成开发环境,它使用 MSBuild 编译 WPF 应用程序,并包括用于创建 UI 的可视化设计器。 有关详细信息,请参阅 使用 Visual Studio 编写和管理代码 和 在 Visual Studio 中设计 XAML。
WPF 生成管道
当生成 WPF 项目时,会调用语言特定的目标和 WPF 特定的目标组合。 执行这些目标的过程称为生成管道,关键步骤如下图所示。
预生成初始化
在生成之前,MSBuild 确定重要工具和库的位置,包括:
.NET Framework。
Windows SDK 目录。
WPF 引用程序集的位置。
程序集搜索路径的属性。
MSBuild 搜索程序集的第一个位置是引用程序集目录(%ProgramFiles%\Reference Assemblies\Microsoft\Framework\v3.0\)。 在此步骤中,生成过程还会初始化各种属性和项组,并执行任何所需的清理工作。
解析引用
生成过程查找并绑定生成应用程序项目所需的程序集。 此逻辑包含在 ResolveAssemblyReference
任务中。 项目文件中声明为 Reference
的所有程序集都被提供给任务,同时也包括系统上已安装程序集的搜索路径和元数据信息。 该任务查找程序集,并使用已安装的程序集的元数据筛选掉那些不需要显示在输出清单中的核心 WPF 程序集。 这样做是为了避免 ClickOnce 清单中的冗余信息。 例如,由于 PresentationFramework.dll 可以被视为基于 WPF 的应用程序的代表,并且所有 WPF 程序集都存在于安装了 .NET Framework 的每台计算机上的同一位置,因此无需在清单中的所有 .NET Framework 引用程序集上包括所有信息。
标记编译第一遍
在此步骤中,将分析并编译 XAML 文件,以便运行时不会花费时间来分析 XML 和验证属性值。 编译的 XAML 文件已预先标记化,因此在运行时,加载它的速度应该比加载 XAML 文件快得多。
在此步骤中,针对作为 Page
生成项的每个 XAML 文件,都会进行以下活动:
XAML 文件由标记编译器分析。
将为该 XAML 创建编译表示形式,并将其复制到 obj\Release 文件夹。
将创建并复制到 obj\Release 文件夹的新分部类的 CodeDOM 表示形式。
此外,还会为每个 XAML 文件生成特定于语言的代码文件。 例如,对于 Visual Basic 项目中的 Page1.xaml 页面,将生成Page1.g.vb;对于 C# 项目中的 Page1.xaml 页面,将生成Page1.g.cs。 文件名中的“.g”表示该文件是自动生成的代码,包含标记文件顶级元素(例如 Page
或 Window
)的部分类声明。 类在 C# 中使用 partial
修饰符(在 Visual Basic 中使用Extends
)声明,以指示该类在其他位置有另一个声明,通常位于代码隐藏文件 Page1.xaml.cs 中。
分部类从相应的基类(如页面的 Page)扩展并实现 System.Windows.Markup.IComponentConnector 接口。 IComponentConnector 接口具有初始化组件的方法,并连接其内容中元素的名称和事件。 因此,生成的代码文件具有如下所示的方法实现:
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater =
new System.Uri(
"window1.xaml",
System.UriKind.RelativeOrAbsolute);
System.Windows.Application.LoadComponent(this, resourceLocater);
}
Public Sub InitializeComponent() _
If _contentLoaded Then
Return
End If
_contentLoaded = True
Dim resourceLocater As System.Uri = _
New System.Uri("mainwindow.xaml", System.UriKind.Relative)
System.Windows.Application.LoadComponent(Me, resourceLocater)
End Sub
默认情况下,标记编译在与 MSBuild 引擎相同的 AppDomain 中运行。 这可提供显著的性能提升。 可以通过 AlwaysCompileMarkupFilesInSeparateDomain
属性来切换此行为。 这有一个优势,通过卸载独立的 AppDomain,可以卸载所有的引用程序集。
标记编译 - 传递 2
并非所有 XAML 页面都是在 XAML 标记编译的第一阶段编译的。 具有本地定义类型引用的 XAML 文件(对同一项目中其他地方的代码中定义的类型的引用)目前不受编译的豁免。 这是因为这些本地定义的类型仅存在于源中,尚未编译。 为了确定这一点,解析器使用启发式方法,这些方法包括在标记文件中查找类似 x:Name
的项目。 找到此类实例时,该标记文件的编译将被推迟,直到代码文件被编译完成,然后,第二次标记编译过程将处理这些文件。
文件分类
生成过程将输出文件放入不同的资源组,具体取决于它们将放置在哪个应用程序程序集中。 在典型的非局部化应用程序中,标记为 Resource
的所有数据文件都放置在主程序集(可执行文件或库)中。 在项目中设置 UICulture
时,所有已编译的 XAML 文件和专门标记为特定于语言的资源都放置在附属资源程序集中。 此外,所有语言中立的资源都放置在主程序集中。 在构建过程的这个步骤中,将做出该决定。
项目文件中的 ApplicationDefinition
、Page
和 Resource
生成操作可以使用 Localizable
元数据(可接受的值为 true
和 false
)进行扩充,这决定了文件是特定于语言还是非特定语言。
核心编译
核心编译步骤涉及代码文件的编译。 这由 Microsoft.CSharp.targets 和 Microsoft.VisualBasic.targets 这两个特定于语言的目标文件中的逻辑进行协调。 如果启发式方法已确定标记编译器的单个传递已足够,则会生成主程序集。 但是,如果项目中的一个或多个 XAML 文件具有对本地定义的类型的引用,则会生成临时 .dll 文件,以便在完成第二次标记编译后创建最终的应用程序程序集。
清单生成
在构建过程结束时,当应用程序的所有程序集和内容文件准备就绪后,将生成该应用程序的 ClickOnce 清单。
部署清单文件描述部署模型:当前版本、更新行为和发布者标识以及数字签名。 此清单旨在由负责部署的管理员撰写。 文件扩展名为 .xbap(适用于 XAML 浏览器应用程序(XBAP)和适用于已安装应用程序的 .application。 前者由 HostInBrowser
项目属性决定,因此清单将应用程序标识为浏览器托管。
应用程序清单(.exe.manifest 文件)描述应用程序程序集和依赖库,并列出应用程序所需的权限。 此文件旨在由应用程序开发人员创作。 为了启动 ClickOnce 应用程序,用户打开应用程序的部署清单文件。
为 XBAP 创建此类清单文件始终不可或缺。 对于已安装的应用程序,除非在项目文件中为 GenerateManifests
属性指定值 true
,否则不会创建这些应用程序。
XBAP 比那些分配给典型 Internet 区域应用程序的权限获得了两个额外的权限:WebBrowserPermission 和 MediaPermission。 WPF 生成系统在应用程序清单中声明这些权限。
增量构建支持
WPF 生成系统为增量生成提供支持。 它非常智能地检测对标记或代码所做的更改,并且只编译受更改影响的这些项目。 增量生成机制使用以下文件:
$(AssemblyName)_MarkupCompiler.Cache 文件,用于维护当前编译器状态。
$(AssemblyName)_MarkupCompiler.lref 文件,用于缓存具有对本地定义类型的引用的 XAML 文件。
下面是一组控制增量生成的规则:
该文件是生成系统检测到更改的最小单位。 因此,对于代码文件,生成系统无法判断类型是否已更改或是否添加了代码。 对于项目文件也是如此。
增量生成机制必须能够识别 XAML 页面定义类或使用其他类。
如果
Reference
条目发生更改,则重新编译所有页面。如果代码文件发生更改,请重新编译具有本地定义类型引用的所有页面。
如果 XAML 文件更改:
如果 XAML 在项目中声明为
Page
:如果 XAML 没有本地定义类型引用,请重新编译 XAML 以及具有本地引用的所有 XAML 页面;如果 XAML 具有本地引用,请重新编译具有本地引用的所有 XAML 页面。如果 XAML 在项目中声明为
ApplicationDefinition
:重新编译所有 XAML 页面(原因:每个 XAML 都引用了可能已更改的 Application 类型)。
如果项目文件将代码文件声明为应用程序定义,而不是 XAML 文件:
检查项目文件中的
ApplicationClassName
值是否已更改(是否有新的应用程序类型?)。 如果是,请重新编译整个应用程序。否则,请使用本地引用重新编译所有 XAML 页面。
如果项目文件发生更改:应用上述所有规则,并查看需要重新编译的内容。 对以下属性的更改会触发完整的重新编译:
AssemblyName
、IntermediateOutputPath
、RootNamespace
和HostInBrowser
。
可以采用以下重新编译方案:
重新编译整个应用程序。
仅重新编译具有本地定义类型引用的 XAML 文件。
未重新编译任何内容(如果项目中没有任何更改)。
另请参阅
- 部署 WPF 应用程序
- WPF MSBuild 参考
- WPF 中的
包 URI - WPF 应用程序资源、内容和数据文件