第 2 章:生成“Longhorn”应用程序

 

简介
第 1 章:“Longhorn”应用程序模型

第 2 章:生成“Longhorn”应用程序

布伦特校长
Wise Owl Consulting

2003 年 11 月

目录

Microsoft .NET 生成引擎:MSBuild.exe
使用 MSBuild 生成Hello World
MSBuild 术语
生成 Longhorn 可执行应用程序
生成 Longhorn 库程序集
生成 Longhorn 文档
作为类声明的 XAML 文件
应用程序清单
部署清单
运行应用程序
为什么要创建另一个生成系统?
总结

若要生成 Longhorn 应用程序,需要安装 Longhorn 软件开发工具包 (SDK) 。 或者,可以安装支持 Longhorn 的 Microsoft® Visual Studio® 版本。 在这本书中,我不讨论如何使用 Visual Studio,因为它的向导、花哨的代码生成功能和项目生成功能掩盖了实际发生的情况。 我相信,在依赖该工具之前,你应该了解工具对你的作用。

安装 Longhorn SDK 时,它会创建一组“开始”菜单项,可用于创建可在其中生成 Longhorn 应用程序的命令提示符会话。 若要在 Microsoft Windows® XP 32 位系统上生成应用程序的调试版本,请浏览以下菜单项以创建相应的命令提示符会话:

  • 开始
  • Programs
  • Microsoft Longhorn SDK
  • 打开“生成环境”窗口
  • Windows XP 32 位生成环境
  • 设置 Windows XP 32 位生成环境 (调试)

Microsoft .NET 生成引擎:MSBuild.exe

MSBuild 是用于生成 Longhorn 应用程序的主要工具。 可以使用 help 命令行选项运行 MSBuild,以获取有关其使用情况的详细信息:

MSBuild /?

在没有任何命令行参数的情况下执行 MSBuild 时,如此处所示,它会在当前工作目录中搜索以“proj”结尾的文件名,例如 .proj、.csproj 等。找到项目时,它会根据该文件中的 指令生成项目。

MSBuild

如果目录中有多个项目文件,则可以在命令行上指定相应的项目文件:

MSBuild <ProjectName>.proj

通常,MSBuild 在项目文件中生成默认目标。 可以重写此并指定要生成的目标。 例如,若要生成名为 CleanBuild 的目标,请按如下所示调用 MSBuild:

MSBuild /t:Cleanbuild

使用 MSBuild 生成Hello World

让我们看一下创建基于导航的简单Hello World应用程序所需的文件。 稍后,我将详细介绍每个文件的用途和用法。

首先,需要定义 Application 对象。 在通常称为 应用程序定义文件的文件中执行此操作。 此 HelloWorldApplication.xaml 文件定义我的 Application 对象。

HelloWorldApplication.xaml

<NavigationApplication xmlns="https://schemas.microsoft.com/2003/xaml" 
                       StartupUri="HelloWorld.xaml" />

此定义显示,“对于我的 Application 对象,我想使用 MSAvalon.Windows.Navigation.NavigationApplication 类的实例。 启动时,应用程序应导航到并在 HelloWorld.xaml 文件中定义的 UI (用户界面) 显示用户界面。

下面是 HelloWorld.xaml 文件的内容。 这是第 1 章中上一个Hello World示例的更有趣的版本。

HelloWorld.xaml

<Border xmlns="https://schemas.microsoft.com/2003/xaml">
  <FlowPanel>
    <SimpleText Foreground="DarkRed" FontSize="14">Hello World!</SimpleText>   </FlowPanel>
</Border>

现在,我已拥有简单Hello World应用程序的所有“代码”,我需要一个定义如何生成应用程序的项目文件。 下面是我的 HelloWorld.proj 文件。

HelloWorld.proj

<Project DefaultTargets="Build">
  <PropertyGroup>
    <Property Language="C#" />   
    <Property DefaultClrNameSpace="IntroLonghorn" />
    <Property TargetName="HelloWorld" />
  </PropertyGroup>

  <!--Imports the target which contains all the common targets-->
  <Import Project="$(LAPI)\WindowsApplication.target" />

  <ItemGroup>
    <!-- Application markup -->
    <Item Type="ApplicationDefinition" Include="HelloWorldApplication.xaml" />
   
    <!-- Compiled Xaml Files list -->
    <Item Type="Pages" Include="HelloWorld.xaml"/>      
  </ItemGroup>
</Project>

将这三个文件放入目录。 打开 Longhorn SDK 命令提示符,导航到包含文件的目录,然后运行 MSBuild。 它将程序编译为可执行文件。

本章稍后将介绍应用程序定义文件的内容。 第 3 章详细介绍了可用于定义 UI 的许多可扩展应用程序标记语言 (XAML) 元素。 在更深入地查看项目文件之前,让我们查看一些 MSBuild 术语。

MSBuild 术语

让我们为一些 MSBuild 元素建立定义。 属性是键值对。 属性的值可以来自环境变量、命令行开关或项目文件中的属性定义,如下所示:

<Property OutputDir="bin\" />

可以将 视为文件数组。 可以包含通配符,并且可以排除特定文件。 MSBuild 使用 ItemType 属性对项进行分类,如下所示:

<Item Type="Compile" Include="*.cs" Exclude="DebugStuff.cs" />

任务是生成过程中的原子单元。 Task 可以接受来自 Property 元素、Item 元素或纯字符串的输入参数。 任务的名称标识执行任务所需的生成 .NET 数据类型。 任务可以发出其他 Task使用的。 MSBuild 包含许多任务,所有这些任务都可以大致分类为

  • .NET 工具任务
  • 部署任务
  • Shell 任务

例如名称Csc 的任务调用 C# 编译器作为生成工具,该工具将 Sources 属性中指定的所有 Item 元素编译 (该属性指定类型为编译) Item 元素,并将程序集生成为输出 Item。

<Task Name="Csc" AssemblyName="$(OutputDir)\HelloWorld.exe"
                 Sources="@(Compile)" />

目标是生成过程中的单个逻辑步骤。 目标可以执行时间戳分析。 这意味着,如果 不需要,则不会运行 Target目标执行一个或多个 Task以执行所需的操作,如下所示:

<Target Name="CopyToServer"
        Inputs="$(OutputDir)\HelloWorld.exe"
        Outputs="\\DeployServer\$(BuildNum)\HelloWorld.exe"
        DependsOnTargets="Compile">

  <Task Name="Copy" ... />
</Target>

Condition 属性大致等效于简单的 if 语句。 条件可以比较两个字符串或检查是否存在文件或目录。 可以将 Condition 应用于项目文件中的任何元素。 例如,下面是一组属性,这些属性仅在 Configuration 属性具有值 Debug 时定义:

<PropertyGroup Condition=" '$(Configuration)'=='Debug' " >
    <Property ... />
    <Property ... />
</PropertyGroup>

Import 大致等效于 C/C++ #include 语句,如以下示例所示。 导入项目时,导入项目的内容在逻辑上成为导入项目的一部分。

<Import Project="$(LAPI)\WindowsApplication.target" />

现在,术语已经不通,让我们来看看一个典型的项目文件。

生成 Longhorn 可执行应用程序

下面是一个简单但相对全面的项目文件,用于生成可执行的 Longhorn 应用程序:

<Project DefaultTargets="Build">
  <PropertyGroup>
    <Property Language="C#" />
    <Property DefaultClrNameSpace="IntroLonghorn" />
    <Property TargetName="MyApp" />
  </PropertyGroup>

  <Import Project="$(LAPI)\WindowsApplication.target" />

  <ItemGroup>
    <Item Type="ApplicationDefinition" Include="MyApp.xaml" />

    <Item Type="Pages" Include="HomePage.xaml" />
    <Item Type="Pages" Include="DetailPage.xaml" />
    <Item Type="Code" Include="DetailPage.xaml.cs"/>

    <Item Type="DependentProjects" Include="MyDependentAssembly.proj" /> 

    <Item Type="Components" Include="SomeThirdParty.dll" />

    <Item Type="Resources" Include="Picture1.jpg"
          FileStorage="embedded" Localizable="False"/>
    <Item Type="Resources" Include="Picture2.jpg"
          FileStorage="embedded" Localizable="True"/>
  </ItemGroup>
</Project>

Project 元素

所有项目文件都以名为 Project 的根元素定义开头。 其 DefaultTargets 属性指定系统在未指定目标时应生成的目标的名称。 在此示例中,我指定默认情况下,系统应生成名为 Build 的目标。

PropertyGroupProperty 元素

生成规则可以根据属性值有条件地执行。 如前所述,属性的值可以来自环境变量、MSBuild 命令行开关或项目文件中的属性定义。

应用程序的项目必须至少指定 LanguageTargetName 属性的值。 在此示例中,我指定语言为 C#,生成的应用程序的名称应为 MyApp。 我还向名为 DefaultClrNameSpace 的属性分配了一个值。

生成系统将每个 XAML 文件编译为托管类定义。 默认情况下,托管类的名称与 XAML 源文件的基文件名相同。 例如,文件 Markup.xaml 编译为名为 Markup 的类的定义。 通过将 DefaultClrNameSpace 属性设置为 IntroLonghorn,我要求生成系统为生成的类名添加 IntroLonghorn 命名空间的前缀。 因此,生成系统为 Markup.xaml 定义生成名为 IntroLonghorn.Markup 的类。

我在导入其他项目之前定义了我的属性,因此导入项目中的规则将使用我指定的属性值,例如,由于将 Language 属性定义为 C#,我将为 C# 应用程序获取正确的生成规则。

Import 元素

生成目标中的规则生成我的 Longhorn 应用程序的可执行文件。 在每个项目文件中指定这些生成规则将是繁琐和重复的。 因此,稍后在项目文件中,我使用以下定义导入名为 WindowsApplication.target 的预定义项目文件:

  <Import Project="$(LAPI)\WindowsApplication.target" />

此导入的文件包含用于生成 Windows 应用程序的标准生成规则,它 (间接) 定义名为 “生成”的目标。

ItemGroupItem 元素

ItemGroup 元素及其子 Item 元素定义生成应用程序所需的所有部分。

必须有一个类型ApplicationDefinition,如下所示。 此项指定用于描述应用程序要使用的 Application 对象的文件。 Application 对象通常是 MSAvalon.Windows.Application 类或 MSAvalon.Windows.Navigation.NavigationApplication 类的实例,我在本章后面对此进行了介绍。

<Item Type="ApplicationDefinition" Include="MyApp.xaml" />

每个具有页面类型的都定义一组 XAML 文件,如下所示。 生成系统将这些 XAML 定义编译为包含在生成的程序集中的类。

<Item Type="Pages" Include="HomePage.xaml" />
<Item Type="Pages" Include="DetailPage.xaml" />

具有 Code类型的每个都表示一个源文件,如下所示。 生成系统使用项目的 Language 属性选择的相应编译器编译这些源文件。

<Item Type="Code" Include="DetailPage.xaml.cs"/>

此项目可能依赖于其他项目。 生成系统必须先编译这些依赖项目,然后才能生成此项目。 使用类型DependentProject 的列出每个此类依赖项目:

<Item Type="DependentProjects" Include="MyDependentAssembly.proj" /> 

此项目中的代码可以使用预生成程序集(也称为 组件程序集)中的类型。 若要使用此类组件程序集编译代码,编译器需要对每个程序集的引用。 此外,在部署应用程序时,还需要部署这些组件程序集。 使用具有组件类型的列出每个组件程序集:

<Item Type="Components" Include="SomeThirdParty.dll" />

引用的程序集与组件程序集稍有不同。 在这两种情况下,代码都使用预生成程序集中的类型。 但是,不会将引用的程序集作为应用程序的一部分交付,而将组件程序集作为应用程序的一部分交付。 生成系统需要知道这一区别。

指定一个引用类型的,以指示编译器必须在生成时引用指定的程序集,如下所示,但程序集不会是应用程序部署的一部分。 生成系统自动包含对标准系统程序集的引用,例如,mscorlib.dll、System.dll、PresentationFramework.dll。 等等 ,但必须添加应用程序必须引用的任何非标准程序集。

<Item Type="References" Include="SharedThirdParty.dll" />

应用程序可能还会使用资源。 资源类型的描述应用程序使用的资源,如下所示。 生成系统可以将资源嵌入到生成的程序集中,或将其作为独立文件包含。 生成系统还可以将可本地化的资源放入附属程序集中。

<Item Type="Resources" Include="Picture1.jpg"
      FileStorage="embedded" Localizable="False"/>
<Item Type="Resources" Include="Picture2.jpg"
      FileStorage="embedded" Localizable="True"/>

生成 Longhorn 库程序集

除了可执行应用程序之外,你还需要生成库。 应用程序项目和库项目之间的主要区别如下:

  • 库项目将 TargetType 属性的值设置为 Library
  • 库项目通常不包括应用程序定义项。

下面是创建库的项目文件的示例:

<Project DefaultTargets="Build">
  <PropertyGroup>
    <Property Language="C#" />
    <Property DefaultClrNameSpace="IntroLonghorn" />
    <Property TargetName="MyLibrary" />
    <Property TargetType="Library" />
  </PropertyGroup>

  <Import Project="$(LAPI)\WindowsApplication.target" />

  <ItemGroup>
    <Item Type="Pages" Include="ErrorPage.xaml" />
    <Item Type="Code" Include="ErrorPage.xaml.cs"/>
    <Item Type="Code" Include="Utilities.cs"/>

    <Item Type="DependentProjects" Include="MyDependentAssembly.proj" /> 

    <Item Type="Components" Include="SomeThirdParty.dll" />

    <Item Type="Resources" Include="Picture1.jpg"
          FileStorage="embedded" Localizable="False"/>
    <Item Type="Resources" Include="Picture2.jpg"
          FileStorage="embedded" Localizable="True"/>
  </ItemGroup>
</Project>

生成 Longhorn 文档

不仅限于使用 XAML 生成应用程序。 还可以使用 XAML 文件创建高度交互式、以智能方式呈现的自适应文档供用户阅读。 在这种情况下,XAML 文件共同表示文档的页面。 可以使用 MSBuild 引擎生成此类文档。

对生成文档而不是应用程序的项目文件所做的更改是次要的:

  • TargetType 属性的值设置为 Document
  • 导入适用于相应生成规则的 WindowsDocument.target 项目。
  • 像往常一样包括所有其他项目文件。

了解 DocumentTargetType 真正生成的内容非常重要。 生成 文档时,生成输出为 .container 文件,生成系统优化容器的内容以供下载而不是速度。 容器文件是 Windows 结构化存储(也称为 DocFile)格式的扩展。 Longhorn 容器处理提供允许呈现部分下载的文件的功能。 因此,在应用程序开始运行之前,不需要下载整个容器。

此外,当你要求 MSBuild 创建容器文件时,它会将每个 XAML 文件编译为 XML 的二进制表示形式,称为二进制 XAML (BAML) 。 BAML 比原始文本文件或编译到 IL 的程序集要紧凑得多。 BAML 文件下载速度更快(已针对下载进行优化),但解释器必须在运行时对其进行分析,以创建文件中所述的类的实例。 因此,此类文件未针对速度进行优化。 到目前为止,我一直在生成编译到 IL 的文件, (也称为 CAML 文件,它是已编译 XAML) 的缩写。

下面是创建电子文档的项目文件示例:

<Project DefaultTargets="Build">
  <PropertyGroup>
    <Property TargetType="Document" />
      <Property Language="C#" />
      <Property DefaultClrNameSpace="IntroLonghorn" />
      <Property TargetName="MyDocument" />
  </PropertyGroup>
    
  <Import Project="$(LAPI)\WindowsDocument.target" />

  <ItemGroup>
    <Item Type="ApplicationDefinition" Include="MyApp.xaml" />

    <Item Type="Pages" Include="Markup.xaml" />
    <Item Type="Pages" Include="Navigate.xaml" />
    <Item Type="Code" Include="Navigate.xaml.cs"/>

    <Item Type="Resources" Include="Picture1.jpg"
          FileStorage="embedded" Localizable="False"/>
    <Item Type="Resources" Include="Picture2.jpg"
          FileStorage="embedded" Localizable="True"/>
  </ItemGroup>
</Project>

了解如何生成各种类型的 Longhorn 应用程序和组件后,让我们更详细地了解 XAML 文件。 具体来说,让我们看看生成系统在将 XAML 文件转换为 .NET 类时会执行哪些操作。

作为类声明的 XAML 文件

应用程序定义文件是定义应用程序的 Application 对象的类的 XAML 文件。 但是,一般来说,XAML 文档只是定义类的文件。 XAML 定义生成的类派生自与 XML 文档的根元素名称关联的类。 默认情况下,生成系统使用 XAML 基文件名作为生成的类名。

为导航应用程序创建应用程序定义文件

回想一下,ApplicationDefinition类型的 Item 元素指定定义 Application 对象的 XAML 文件的名称。 换句话说,此元素指定包含应用程序的入口点的 XAML 文件。 Longhorn 平台将创建在此文件中定义的 MSAvalon.Windows.Application 派生类型的实例,并允许它管理应用程序的启动、关闭和导航。

第 1 章介绍了如何以编程方式创建和使用应用程序实例。 以下 XAML 文件使用标记为项目定义 Application 对象:

<NavigationApplication xmlns="https://schemas.microsoft.com/2003/xaml" 
                       StartupUri="HelloWorld.xaml" />

我预计大多数 Longhorn 应用程序都是基于导航的应用程序,因此,通常只重复使用标准 NavigationApplication 对象。 只需更改 StartupUri 属性的值,即可对大多数基于导航的应用程序重复使用此应用程序定义文件。

例如,如果上一个应用程序定义驻留在 HelloWorldApplication.xaml 文件中,并且我使用前面列出的 HelloWorld.proj 项目文件,则生成系统将生成以下类声明:

namespace IntroLonghorn {
  class HelloWorldApplication :
           MSAvalon.Windows.Navigation.NavigationApplication {
.
.
.
   }
 }

命名空间来自项目文件中 的 DefaultClrNameSpace 声明,声明的类名与基文件名相同,声明的类扩展 XAML 文件中根元素表示的类。

使用属性自定义生成的代码

在 XAML 文件中声明根元素时,可以使用根元素上的属性来控制生成的类声明的名称。 可以使用以下任一可选属性:

  • 将前缀与名为 Definition 的命名空间关联的命名空间 前缀定义。 必须为此命名空间定义前缀才能使用 语言 属性。 传统上,使用 def 前缀。
  • (Definition 命名空间) 中定义的 Language 属性,该属性指定 XAML 文件中任何内联代码使用的编程语言。
  • (在 Definition 命名空间) 中定义的 Class 属性,该属性指定生成的类的名称。 指定包含一个或多个句点的名称时,生成系统不会在名称前面加上 DefaultClrNameSpace 值。

例如,我们将 HelloWorldApplication.xaml 文件的内容更改为以下内容:

<NavigationApplication xmlns="https://schemas.microsoft.com/2003/xaml"
                       xmlns:def="Definition"
                       def:Class="Special.MyApp"
                       def:CodeBehind="HelloWorldApplication.xaml.cs" 
                       StartupUri="HelloWorld.xaml" />

生成的类将如下所示:

namespace Special {
  class MyApp :
           MSAvalon.Windows.Navigation.NavigationApplication {
.
.
.
  }
}

在同一类中使用代码和标记

除了指定 UI 的标记外,几乎所有应用程序都需要你编写一些代码(例如,按钮的单击事件处理程序或虚拟方法替代)。 回想一下第 1 章,我的非基于导航的应用程序会重写 OnStartingUp 方法,以创建其窗口和控件。 我将重写该示例,以说明如何组合应用程序代码和标记。

虽然即将推出的此示例将创建一个非导航应用程序,但我想强调的是,创建此类应用程序确实没有令人信服的理由。 始终可以创建从不实际导航到其他页面的基于导航的应用程序。 但是,编写此类应用程序需要在同一类中混合代码和标记,因此提供了一个很好的示例。

回想一下,创建非导航应用程序需要定义从 MSAvalon.Windows.Application 继承并替代 OnStartingUp 方法的自定义类。 应用程序定义文件声明程序使用的应用程序对象类。 因此,非导航应用程序必须在同一类中定义其重写 的 OnStartingUp 方法。

除以下更改外,非导航应用程序的应用程序配置文件包含与导航应用程序的定义文件相同的项:

  • 根元素为 Application ,而不是 NavigationApplication
  • 该文件必须包含或引用应用程序类的 OnStartingUp 方法的实现。

由于我需要使用标记和代码来实现单个类,因此我需要向你展示一种将源代码文件与 XAML 文件关联的技术。

将Source-Behind文件与 XAML 文件关联

你经常需要使用标记开发应用程序的各个部分,并使用更传统的编程语言开发其他部件。 我强烈建议使用以下技术将 UI 和逻辑分离到单独的源文件中。

可以将定义命名空间中定义的 XAML CodeBehind 元素 () 添加到任何 XAML 文件的根元素,并指定源代码文件的名称 (也称为代码隐藏文件) 。 生成引擎会将 XAML 声明编译为托管类。 生成系统还会将代码隐藏文件编译为托管类声明。 棘手的方面是,这两个类声明都表示单个类的分部声明。

下面是一个 XAML 定义,它生成与第 1 章中的第一个示例等效的非导航应用程序类:

<Application xmlns="https://schemas.microsoft.com/2003/xaml"
             xmlns:def="Definition"
             def:Language="C#"
             def:Class="IntroLonghorn.CodeBehindSample"
             def:CodeBehind="CodeBehind.xaml.cs" />

此应用程序定义文件有两个值得注意的方面:

  • Language 属性指定代码隐藏文件包含 C# 源代码。
  • CodeBehind 属性指定源文件名称为 CodeBehindMySample2.xaml.cs。

下面是匹配的源隐藏文件:

namespace IntroLonghorn {
  using System;
  using MSAvalon.Windows;
  using MSAvalon.Windows.Controls;
  using MSAvalon.Windows.Media;

  public partial class CodeBehindSample {
    MSAvalon.Windows.Controls.SimpleText txtElement;
    MSAvalon.Windows.Window              mainWindow;

    protected override
    void OnStartingUp (StartingUpCancelEventArgs e) {
      base.OnStartingUp (e);
      CreateAndShowMainWindow ();
    }

    private void CreateAndShowMainWindow () {
      // Create the application's main window
      mainWindow = new MSAvalon.Windows.Window ();

      // Add a dark red, 14 point, "Hello World!" text element
      txtElement = new MSAvalon.Windows.Controls.SimpleText ();
      txtElement.Text = "Hello World!";
      txtElement.Foreground = new
       MSAvalon.Windows.Media.SolidColorBrush (Colors.DarkRed);
      txtElement.FontSize = new FontSize (14,
                                          FontSizeType.Point);
      mainWindow.Children.Add (txtElement);
      mainWindow.Show ();
    }
  }
}

请注意代码隐藏文件中类声明中的部分关键字 (keyword) 。 此关键字 (keyword) 声明编译器应将此类定义与同一类的其他定义合并。 这使你可以提供类的多个分部定义,每个部分定义都位于单独的源文件中,编译器将这些定义合并到生成的程序集中的单个类定义中。

在单个 XAML 文件中混合源代码和标记

我认为在同一文件中混合源代码和标记是错误的。 我甚至考虑不告诉你如何做到这一点。 但是,某些邪恶者会使用此方法编写示例程序,因此您可能需要了解他所做的工作。 此外,还可以使用前面所述的代码隐藏文件方法,使世界摆脱少量的邪恶,并将 UI 与逻辑分开。

下面是一个应用程序定义文件,其中包含使用 标记直接内联插入的源代码:

<Application xmlns="https://schemas.microsoft.com/2003/xaml"
    xmlns:def="Definition"
    def:Language="C#"
    def:Class="IntroLonghorn.MySample2" >

  <def:Code>
  <![CDATA[
    protected override void OnStartingUp (StartingUpCancelEventArgs e) {
      base.OnStartingUp (e);
      CreateAndShowMainWindow ();
    }
    . . . Remaining methods elided for clarity
  ]]>
  </def:Code>
</Application>

在此示例中, Language 属性指定内联源代码为 C#。 请注意, Code 元素是包含内联源代码的 CDATA 块。 有时,在技术上有必要将内联源代码包含在 XML CDATA 块中,以确保文档格式良好。 事实上,XAML 分析程序始终要求你将内联源代码包含在 CDATA 块中,即使省略它生成格式正确的文档也是如此。

我再次为向你展示如此嘲弄而道歉。

应用程序清单

编译应用程序时,MSBuild 会生成.exe文件以及两个清单文件:应用程序清单 *.manifest 和部署清单 *.deploy。 从服务器部署应用程序或文档时,可以使用这些清单。 首先,将应用程序及其所有依赖项以及两个清单文件复制到服务器上的相应位置。 其次,编辑部署清单,使其指向应用程序清单的位置。

为了完整性,我们来看一下应用程序和部署清单的示例。 以下示例中显示的应用程序清单实际上不像部署清单那样有趣。 应用程序清单仅定义构成应用程序的所有部分。 MSBuild 生成应用程序时会生成应用程序清单,而你通常很少修改或完全不修改该应用程序清单。

HelloWorld.manifest

<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
          xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd">

  <assemblyIdentity name="HelloWorld" version="1.0.0.0"
                    processorArchitecture="x86" asmv2:culture="en-us"
                    publicKeyToken="0000000000000000" />

  <entryPoint name="main" xmlns="urn:schemas-microsoft-com:asm.v2"
              dependencyName="HelloWorld">

    <commandLine file="HelloWorld.exe" parameters="" />
  </entryPoint>

  <TrustInfo xmlns="urn:schemas-microsoft-com:asm.v2" xmlns:temp="temporary">
    <Security>
      <ApplicationRequestMinimum>
        <PermissionSet class="System.Security.PermissionSet" version="1" 
                       ID="SeeDefinition">
          <IPermission 
            class="System.Security.Permissions.FileDialogPermission"
            version="1" Unrestricted="true" />
          <IPermission 
            class="System.Security.Permissions.IsolatedStorageFilePermission" 
            version="1" Allowed="DomainIsolationByUser" UserQuota="5242880" />
          <IPermission
            class="System.Security.Permissions.SecurityPermission"
            version="1" Flags="Execution" />
          <IPermission
            class="System.Security.Permissions.UIPermission" version="1"
            Window="SafeTopLevelWindows" Clipboard="OwnClipboard" />
          <IPermission
            class="System.Security.Permissions.PrintingPermission"
            version="1" Level="SafePrinting" />
          <IPermission
            class="MSAvalon.Windows.AVTempUIPermission, PresentationFramework,
                   Version=6.0.4030.0, Culture=neutral,
                   PublicKeyToken=a29c01bbd4e39ac5" version="1"
                   NewWindow="LaunchNewWindows" FullScreen="SafeFullScreen" />
        </PermissionSet>

        <AssemblyRequest name="HelloWorld"
                         PermissionSetReference="SeeDefinition" />
      </ApplicationRequestMinimum>
    </Security>
  </TrustInfo>

  <dependency asmv2:name="HelloWorld">
    <dependentAssembly>
      <assemblyIdentity name="HelloWorld" version="0.0.0.0"
                        processorArchitecture="x86" />
    </dependentAssembly>

    <asmv2:installFrom codebase="HelloWorld.exe"
                       hash="5c58153494c16296d9cab877136c3f106785bfab" 
                       hashalg="SHA1" size="5632" />
  </dependency>
</assembly>

应用程序清单的大多数内容应相对明显。 entryPoint 元素指定入口点方法的名称,main,并引用包含入口点的名为 HelloWorld依赖项entryPoint 元素还包含 shell 运行应用程序所需的程序名称和命令行参数。

HelloWorld依赖元素包含 (dependentAssembly 元素) 的信息,该元素指定依赖程序集和一个 installFrom 元素,该元素指示加载程序在何处查找程序集的文件和文件的原始哈希。 加载程序可以使用 哈希来检测编译后对程序集所做的更改。

Longhorn 信任管理器使用 TrustInfo 元素来确定应用程序所需的安全权限。 在前面的示例中,我的 HelloWorld 应用程序定义了一组权限,它将其命名为 SeeDefinition 权限集。 在我定义权限集后, AssemblyRequest 元素立即请求名为 HelloWorld 的程序集在名为 SeeDefinition 的集中至少接收一组权限。 此示例中的权限是通常授予在 SEE 中运行的应用程序的权限,因此Hello World应用程序在运行时不会向用户显示任何信任管理器安全警告。

部署清单

如前所述,部署清单更有趣。 部署清单显然包含控制应用程序部署的设置。

HelloWorld.deploy

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" 
          xmlns:asmv2="urn:schemas-microsoft-com:asm.v2"  
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd">
  
  <assemblyIdentity name="HelloWorld.deploy" version="1.0.0.0" 
                    processorArchitecture="x86" asmv2:culture="en-us" 
                    publicKeyToken="0000000000000000" />

  <description asmv2:publisher="Wise Owl, Inc." 
               asmv2:product="Brent's HelloWorld Application"            
    asmv2:supportUrl="http://www.wiseowl.com/AppServer/HelloWorld/support.asp" 
  />
  
  <deployment xmlns="urn:schemas-microsoft-com:asm.v2" 
              isRequiredUpdate="false">
    <install shellVisible="true" />
    <subscription>
      <update>
        <beforeApplicationStartup />
        <periodic>
          <minElapsedTimeAllowed time="6" unit="hours" />
          <maxElapsedTimeAllowed time="1" unit="weeks" />
        </periodic>
      </update>
    </subscription>
  </deployment>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity name="HelloWorld" version="1.0.0.0" 
                        processorArchitecture="x86" asmv2:culture="en-us" 
                        publicKeyToken="0000000000000000" />
    </dependentAssembly>
    <asmv2:installFrom codebase="HelloWorld.manifest" />
  </dependency>
</assembly>

部署清单包含 Longhorn 安装和更新应用程序所需的信息。 请注意,部署清单的 assemblyIdentity 元素引用应用程序清单。 毕竟,应用程序清单已经描述了应用程序的所有组件。 若要安装应用程序,部署清单实际上显示“下面是安装此应用程序所需的文件的说明。

当然,在安装应用程序时,还需要更多信息,而不仅仅是要复制到系统上的文件。 description 元素列出了发布者产品和supportUrl 属性;系统会在“添加/删除程序”对话框中显示其内容。

deployment 元素指定如何在部署后部署和更新应用程序。 在此示例中,应用程序对 shell 可见,客户端的系统将检查,并在必要时在用户每次启动应用程序时下载新版本的应用程序。 此外,系统定期(不超过每六小时一次,每周不少于一次)检查新版本。 当定期检查找到新版本时,Longhorn 将在后台下载新版本并安装它;然后,它将准备好在用户下次执行应用程序时运行。

运行应用程序

通常,用户将“运行”应用程序清单以直接从服务器执行应用程序,而无需在本地计算机上安装该应用程序。 Longhorn 根据需要下载应用程序的组件。 在这种情况下,服务器必须可用于运行应用程序。

当用户“运行”部署清单时,Longhorn 将下载应用程序并将其安装到本地计算机上。 应用程序可以在桌面上安装图标、添加“开始”菜单项,并通常成为传统的已安装应用程序。 当然,还可以获得自动后台更新、版本回滚和卸载支持。

首次启动部署清单时,Longhorn 会在应用程序缓存中安装应用程序,并将一个条目添加到控制面板的“添加或删除程序”列表中。 随后,每当运行部署清单时,应用程序将直接从应用程序缓存加载。 它通常不会再次下载。

但是,使用控制面板的“添加/删除程序”小程序从缓存中卸载应用程序时,随后执行部署清单会下载并再次安装该应用程序。

或者,可以在服务器上更改应用程序的版本号。 然后,运行部署清单时,Longhorn 将与当前版本并行下载并安装新版本。 应用程序的两个版本都将显示在“添加或删除程序”列表中。

为什么要创建另一个生成系统?

我真的很喜欢 MSBuild,尽管在撰写本文时,我只有几周的经验。 当然,多年的生成文件经验使任何更优雅的生成系统都具有吸引力。 目前,有两种常用的替代生成系统: Make 和 Ant。 将 MSBuild 与此类替代项进行比较似乎很自然。

为什么不使用 Make?

当许多开发人员熟悉名为 Make 的现有生成系统时,为什么要开发新的生成系统? 使 工具与生成系统的集成效果不佳。 使只是执行 shell 命令。 因此,在生成过程中,一个工具无法与另一个工具通信。 MSBuild 创建 Task 类的实例,任务可以在它们之间通信,传递正常的 .NET 类型。

生成文件具有不同寻常的语法,难以编写,并且无法很好地缩放,因为它们对于大型项目而言会变得复杂。 此外,Make 以外的工具无法轻松处理生成文件。 除 MSBuild 以外的工具可以轻松生成和分析 MSBuild 项目的基于 XML 的语法。

最后,Make 并不真正支持项目。 没有文件系统抽象,也不支持级联属性。 此外,没有设计时支持生成文件。

为什么不使用蚂蚁?

一个类似的常见问题是,当现有非常成功且丰富的系统称为 Ant 时,为什么要开发基于 XML 的新生成系统? Ant 是一个 Java、开放源代码生成系统,来自 Apache.org,该系统率先将基于 XML 的项目文件和任务作为原子生成操作。 nant.sourceforge.net 中还提供了一个名为 NAnt 的出色 .NET Ant 端口。 从表面上看,MSBuild 和 Ant/NAnt 是相似的。 这两种工具都使用 XML 作为其项目序列化格式,这两种工具都使用任务作为其生成操作的原子单元。 这两种工具都有其优点,但当你仔细观察时,它们有不同的设计目标。

Ant 做出设计决定,将大量功能放入一组大型任务中。 MSBuild 具有不同的设计目标,其中类似功能由引擎 (封装,例如时间戳分析、通过项的任务间通信、任务批处理、项转换等) 。 这两种方法都有其优缺点。

Ant 的模型允许开发人员扩展和控制生成的每个细节,因此非常灵活。 不过,这也给任务编写者带来了更大的责任,因为任务需要更加复杂,以提供一致的功能。 MSBuild 的模型减少了每个任务需要实现的功能量。 因此,项目作者可以依赖于跨不同项目、目标和任务的一致功能。 此外,集成开发环境(如 Visual Studio)还可以依赖这些服务来提供一致的结果和丰富的用户体验,而无需了解生成过程中调用的任务的内部情况。

同样,虽然 Ant 具有生成脚本的概念,但它没有 MSBuild 具有的项目清单的概念。 生成脚本说明如何创建一组文件,但不提供描述文件使用方式的其他语义。 清单还描述了文件的语义,它允许其他工具(如 IDE)与生成系统进行更深入的集成。 相反,缺少项目清单意味着开发人员可以更轻松地定制 Ant 来生成新型“内容”,因为生成脚本没有约束架构。

总结

你现在已经掌握了基础知识。 可以编写 XAML,并且可以编译、部署和运行生成的应用程序。 遗憾的是,到目前为止,你学习编写的应用程序非常无聊。 第 3 章 深入探讨 XAML,并演示如何使用 Longhorn 平台提供的各种 UI 对象。 后面的章节介绍了也可以在应用程序中使用的其他一些新技术。

继续学习第 3 章:控件和 XAML