编译 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 相关的目标组合。 执行这些目标的过程称为生成管道,关键步骤如下图所示。

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 引用程序集上包括所有信息。

标记编译 - 第 1 遍

在此步骤中,将分析并编译 XAML 文件,以便运行时不会花费时间来分析 XML 和验证属性值。 编译的 XAML 文件已预先标记化,因此在运行时,加载它的速度应该比加载 XAML 文件快得多。

在此步骤中,对于作为 Page 生成项的每个 XAML 文件,将进行以下活动:

  1. XAML 文件由标记编译器分析。

  2. 将为该 XAML 创建编译表示形式,并将其复制到 obj\Release 文件夹。

  3. 将新的分部类的 CodeDOM 表示形式创建并复制到 obj\Release 文件夹中。

此外,还会为每个 XAML 文件生成特定于语言的代码文件。 例如,对于 Visual Basic 项目中的 Page1.xaml 页面,将生成Page1.g.vb;对于 C# 项目中的 Page1.xaml 页面,将生成Page1.g.cs。 文件名中的“.g”表明该文件是生成代码,其中包含标记文件顶级元素(例如 PageWindow)的分部类声明。 类用修饰符partial在 C# 中声明(在 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程序集来卸载所有引用程序集。

标记编译——第二遍

并非所有 XAML 页面都是在标记编译的第一阶段编译的。 具有本地定义类型引用的 XAML 文件(对同一项目中其他地方的代码中定义的类型的引用)目前不受编译的豁免。 这是因为这些本地定义的类型仅存在于源中,尚未编译。 为了确定这一点,分析程序使用启发式方法,查找标记文件中的类似x:Name的项。 找到此类实例时,标记文件的编译将推迟到代码文件编译,之后,第二个标记编译将处理这些文件。

文件分类

生成过程将输出文件放入不同的资源组,具体取决于它们将放置在哪个应用程序程序集中。 在典型的非局部化应用程序中,标记为 Resource 的所有数据文件都放置在主程序集(可执行文件或库) 中。 在项目中设置UICulture后,所有编译的 XAML 文件和那些专门标记为特定语言的资源都放置在附属资源程序集。 此外,所有非特定语言的资源都放置在主程序集中。 在生成过程的此步骤中,将做出该决定。

项目文件中的ApplicationDefinition, Page, Resource生成操作可以使用Localizable元数据进行扩充(可接受的值为truefalse),这决定了文件是特定于语言还是语言中立。

核心编译

核心编译步骤涉及代码文件的编译。 这种协调由 Microsoft.CSharp.targets 和 Microsoft.VisualBasic.targets 文件中与语言相关的逻辑实现。 如果启发式方法已确定标记编译器的单个传递已足够,则会生成主程序集。 但是,如果项目中的一个或多个 XAML 文件具有对本地定义的类型的引用,则会生成临时 .dll 文件,以便在完成第二次标记编译后创建最终的应用程序程序集。

清单生成

在生成过程结束时,在应用程序程序集和内容文件准备就绪后,将生成应用程序的 ClickOnce 清单。

部署清单文件描述部署模型:当前版本、更新行为和发布者标识以及数字签名。 此清单旨在由处理部署的管理员创作。 文件扩展名为 .xbap(适用于 XAML 浏览器应用程序(XBAP)和适用于已安装应用程序的 .application。 前者由 HostInBrowser 项目属性决定,因此清单将应用程序标识为浏览器托管。

应用程序清单(.exe.manifest 文件)描述应用程序程序集和依赖库,并列出应用程序所需的权限。 此文件旨在由应用程序开发人员创作。 为了启动 ClickOnce 应用程序,用户打开应用程序的部署清单文件。

始终会为 XBAP 创建这些清单文件。 对于已安装的应用程序,除非在项目文件中指定了GenerateManifests属性且设定了值true,否则不会创建它们。

XBAPs 除了典型 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 页面。

  • 如果项目文件发生更改:应用上述所有规则,并查看需要重新编译的内容。 对以下属性的更改会触发完整的重新编译:AssemblyNameIntermediateOutputPath、和RootNamespaceHostInBrowser

可以采用以下重新编译方案:

  • 重新编译整个应用程序。

  • 仅重新编译具有本地定义类型引用的 XAML 文件。

  • 未重新编译任何内容(如果项目中没有任何更改)。

另请参阅