WPFプログラムのコンパイルプロセス

免責事項:この記事はブロガーオリジナル記事です、続くBY-SAのCC 4.0を著作権契約、複製、元のソースのリンクと、この文を添付してください。
このリンク: https://blog.csdn.net/WPwalter/article/details/100156343

SDKのプロジェクトに基づいてコンパイルすると、このプロジェクトは、SDKに含まれているファイルに小道具やターゲットファイルを使用してコンパイルされます。SDK WPFプロジェクトのMicrosoft.NET.Sdk.WindowsDesktopは、コンパイルプロセスが含まれています。

この記事では、WPF、追加の目標は、コンパイルプロセスを追加することで、どのようにこれらの拡張機能は、WPFプロジェクトのプロセスをステップバイステップゴールをコンパイルコンパイルこれらの拡張子を含む、コンパイル処理とWPFのプロジェクトを説明しています。


予め用意

この記事を読む前に、最後にコンパイルプロセスが似ている、事前に知っておく必要があるかもしれません。あなたは読むことができます:

あなたが(例えば、標的/タスクなど)上記の記事中の用語のいくつかを理解していない場合は、この資料の残りの部分を理解することができませんでした。

また、本書の内容に加えて、独自のコンパイルプロセスを探索することができますを参照します。

WPF Microsoft.WinFx.targetsファイル内のコードをコンパイルし、あなたはこの1つのブログ上でこのファイルを見つけることができます。これらはWPFのプログラムをコンパイルするためにどのように連携するか次に、我々はファイルのビルドターゲット(目標)内部の一つ一つを紹介します、そして統一目標です。

Microsoft.WinFx.targetsは、ソースコードを表示することができます:

ターゲット

コンパイル時のWPFはもちろん、実際にタスクタスクを実行するために使用されるこれらのターゲット、ターゲットを実行します。

ターゲットが名前を知っている、あなたは、WPFのビルドプロセスを拡張することができ、タスク名を知って、それが実際のコンパイルプロセスが何をすべきかを理解するのに役立ちます。

この記事では、一覧表示されます。

FileClassification

  • ターゲット名:FileClassification
  • タスク名:FileClassifier

アセンブリに埋め込むためのリソース。リソースがローカライズされていない場合は、本体に埋め込まれ、そこにローカライズされている場合は、サテライトアセンブリに埋め込まれました。

WPFのプロジェクトでは、ターゲットが実行されます。しかし、内部のタスクがあるありますResourceアイテムの種類が実行されるコンパイルするための時間。

GenerateTemporaryTargetAssembly

ターゲット名とタスク同じ名前、ですGenerateTemporaryTargetAssembly

限りあらゆるタイプのページ生成されたXAMLファイルを含むプロジェクトとして、このターゲットが実行されます。

仮組複合体を生成するための理由は、コンパイルプロセスは、この記事の後半で学習するWPFプログラムの一部を読むことができます。

MarkupCompilePass1

ターゲット名とタスク同じ名前、ですMarkupCompilePass1

ローカライズされていないXAMLファイルをバイナリ形式にコンパイル。

MarkupCompilePass2

ターゲット名とタスク同じ名前、ですMarkupCompilePass2

XAMLファイルは、第二ラウンドをコンパイルしますが、今回は同じプログラムの参照型に焦点を当てます。

DesignTimeMarkupCompilation

コンパイラのターゲットの実行は、直接呼び出されるとき、ターゲットデザイナーの実行を行うことになるがある場合にのみそれはありますMarkupCompilePass1

あなたは、Visual Studioでプロジェクトをコンパイルする場合は実際には、あなたがターゲットに呼び出されます。そして、コンパイルされたメソッドは、Visual Studioで見つけることができるかどうかを決定します。

<Target Name="DesignTimeMarkupCompilation">

    <!-- Only if we are not actually performing a compile i.e. we are in design mode -->
    <CallTarget Condition="'$(BuildingProject)' != 'true'"
                Targets="MarkupCompilePass1" />
</Target>

MergeLocalizationDirectives

ターゲット名とタスク同じ名前、ですMergeLocalizationDirectives

アセンブリ全体で単一のファイルにXAMLバイナリ形式にローカライズプロパティと1つまたは複数のファイルを注意してください。

<Target Name="MergeLocalizationDirectives"
        Condition="'@(GeneratedLocalizationFiles)' !=''"
        Inputs="@(GeneratedLocalizationFiles)"
        Outputs="$(IntermediateOutputPath)$(AssemblyName).loc"
>
    <MergeLocalizationDirectives GeneratedLocalizationFiles="@(GeneratedLocalizationFiles)"
                                OutputFile="$(IntermediateOutputPath)$(AssemblyName).loc"/>

    <!--
        Add the merged loc file into _NoneWithTargetPath so that it will be copied to the
        output directory
    -->
    <CreateItem Condition="Exists('$(IntermediateOutputPath)$(AssemblyName).loc')"
                Include="$(IntermediateOutputPath)$(AssemblyName).loc"
                AdditionalMetadata="CopyToOutputDirectory=PreserveNewest; TargetPath=$(AssemblyName).loc" >
        <Output ItemName="_NoneWithTargetPath" TaskParameter="Include"/>
        <Output ItemName="FileWrites" TaskParameter="Include"/>

    </CreateItem>

</Target>

MainResourcesGeneration、SatelliteResourceGeneration

  • ターゲットは2を持っている、MainResourcesGenerationSatelliteResourceGeneration主なリソースの生成やリソース世代の子会社を担当しました。
  • タスク名:ResourcesGenerator

1つまたは複数のリソース(バイナリ形式.JPG、.ICO、.BMP、XAML、および拡張の他のタイプ)埋め込み.resourcesファイルのファイル。

CheckUid、UpdateUid、RemoveUid

  • ターゲットは3を持っているCheckUidUpdateUidRemoveUid主なリソースの生成やリソース世代の子会社を担当しました。
  • タスク名:ResourcesGenerator

チェックし、更新またはすべてのXAML要素は、ローカライズされたXAMLファイルように、UIDを削除します。

<Target Name="CheckUid"
        Condition="'@(Page)' != '' or '@(ApplicationDefinition)' != ''">

    <UidManager MarkupFiles="@(Page);@(ApplicationDefinition)" Task="Check" />

</Target>
<Target Name="UpdateUid"
        Condition="'@(Page)' != '' or '@(ApplicationDefinition)' != ''">

    <UidManager MarkupFiles="@(Page);
                            @(ApplicationDefinition)"
                IntermediateDirectory ="$(IntermediateOutputPath)"
                Task="Update" />

</Target>
<Target Name="RemoveUid"
        Condition="'@(Page)' != '' or '@(ApplicationDefinition)' != ''">
    <UidManager MarkupFiles="@(Page);
                            @(ApplicationDefinition)"

                IntermediateDirectory ="$(IntermediateOutputPath)"
                Task="Remove" />

</Target>

UpdateManifestForBrowserApplication

XAMLブラウザーベースのプロジェクトをコンパイルすると、ファイルをマニフェストプロファイルを追加します<hostInBrowser />

WPFプログラムのコンパイルプロセス

編集アイコン

主にWPFいくつかの重要なターゲット上に列挙したものを対象に、時間をコンパイルターゲットコンパイラの実用化には多くのがあるでしょう。また、通常のコンパイル処理ではなく、専用ビルドプロセスで実行されるいくつかではありません。

WPFプログラムのコンパイルプロセス

地図の読み込み方法はこれです:

  1. 箭头代表依赖关系,如 CoreCompile 有一个指向 DesignTimeMarkupCompilation 的箭头,表示 CoreCompile 执行前会确保 DesignTimeMarkupCompilation 执行完毕;
  2. 如果一个 Target 有多个依赖,则这些依赖会按顺序执行还没执行的依赖,如 PrepareResources 指向了多个 Target MarkupCompilePass1GenerateTemporaryTargetAssemblyMarkupCompilePass2AfterMarkupCompilePass2CleanupTemporaryTargetAssembly,那么在 PrepareResources 执行之前,如果还有没有执行的依赖,会按顺序依次执行;
  3. WPF 所有的 Target 扩展都是通过依赖来指定的,也就是说必须基于现有的核心编译过程,图中从绿色或黄色的节点向前倒退的所有依赖都会被执行。

各种颜色代表的含义:

  • 蓝色,表示 WPF 扩展的 Target
  • 浅蓝色,表示 WPF 扩展的 Target,但是没有执行任何实际的任务,只是提供一个扩展点
  • 绿色,表示核心的编译过程,但是被 WPF 编译过程重写了
  • 黄色,表示核心的编译过程(即便不是 WPF 程序也会执行的 Target)
  • 浅黄色,表示在这张图里面不关心的 Target(不然整个画下来就太多了)
  • 紫色,仅在 Visual Studio 编译期间会执行的 WPF 扩展的 Target

编译过程描述

我们都知道 XAML 是可以引用 CLR 类型的;如果 XAML 所引用的 CLR 类型在其他被引用的程序集,那么编译 XAML 的时候就可以直接引用这些程序集,因为他们已经编译好了。

但是我们也知道,XAML 还能引用同一个程序集中的 CLR 类型,而此时这个程序集还没有编译,XAML 编译过程并不知道可以如何使用这些类型。同时我们也知道 CLR 类型可是使用 XAML 生成的类型,如果 XAML 没有编译,那么 CLR 类型也无法正常完成编译。这是矛盾的,这也是 WPF 扩展的编译过程会比较复杂的原因之一。

WPF 编译过程有两个编译传递,MarkupCompilePass1MarkupCompilePass2

MarkupCompilePass1 的作用是将 XAML 编译成二进制格式。如果 XAML 文件包含 x:Class 属性,那么就会根据语言生成一份代码文件;对于 C# 语言,会生成“文件名.g.cs”文件。但是 XAML 文件中也有可能包含对同一个程序集中的 CLR 类型的引用,然而这一编译阶段 CLR 类型还没有开始编译,因此无法提供程序集引用。所以如果这个 XAML 文件包含对同一个程序集中 CLR 类型的引用,则这个编译会被推迟到 MarkupCompilePass2 中继续。而在 MarkupCompilePass1MarkupCompilePass2 之间,则插入了 GenerateTemporaryTargetAssembly 这个编译目标。

GenerateTemporaryTargetAssembly 的作用是生成一个临时的程序集,这个临时的程序集中包含了 MarkupCompilePass1 推迟到 MarkupCompilePass2 中编译时需要的 CLR 类型。这样,在 MarkupCompilePass2 执行的时候,会获得一个包含原本在统一程序集的 CLR 类型的临时程序集引用,这样就可以继续完成 XAML 格式的编译了。在 MarkupCompilePass2 编译完成之后,XAML 文件就完全编译完成了。之后,会执行 CleanupTemporaryTargetAssembly 清除之前临时编译的程序集。

编译临时程序集时,会生成一个新的项目文件,名字如:$(项目名)_$(随机字符)_wpftmp.csproj,在与原项目相同的目录下。

在需要编译一个临时程序集的时候,CoreCompile 这样的用于编译 C# 代码文件的编译目标会执行两次,第一次是编译这个临时生成的项目,而第二次才是编译原本的项目。

现在,我们看一段 WPF 程序的编译输出,可以看到看到这个生成临时程序集的过程。

一時的なプロジェクトやアセンブリを生成するには

随后,就是正常的其他的编译过程。

关于临时生成程序集

在 WPF 的编译过程中,我想单独将临时生成程序集的部分进行特别说明。因为如果你不了解这一部分的细节,可能在未来的使用中遇到一些临时生成程序集相关的坑。

下面这几篇博客就是在讨论其中的一些坑:

我需要摘抄生成临时程序集的一部分源码:

<PropertyGroup>
    <_CompileTargetNameForLocalType Condition="'$(_CompileTargetNameForLocalType)' == ''">_CompileTemporaryAssembly</_CompileTargetNameForLocalType>
</PropertyGroup>

<Target Name="_CompileTemporaryAssembly"  DependsOnTargets="BuildOnlySettings;ResolveKeySource;CoreCompile" />

<Target Name="GenerateTemporaryTargetAssembly"
        Condition="'$(_RequireMCPass2ForMainAssembly)' == 'true' " >

    <Message Text="MSBuildProjectFile is $(MSBuildProjectFile)" Condition="'$(MSBuildTargetsVerbose)' == 'true'" />

    <GenerateTemporaryTargetAssembly
            CurrentProject="$(MSBuildProjectFullPath)"
            MSBuildBinPath="$(MSBuildBinPath)"
            ReferencePathTypeName="ReferencePath"
            CompileTypeName="Compile"
            GeneratedCodeFiles="@(_GeneratedCodeFiles)"
            ReferencePath="@(ReferencePath)"
            IntermediateOutputPath="$(IntermediateOutputPath)"
            AssemblyName="$(AssemblyName)"
            CompileTargetName="$(_CompileTargetNameForLocalType)"
            GenerateTemporaryTargetAssemblyDebuggingInformation="$(GenerateTemporaryTargetAssemblyDebuggingInformation)"
            >

    </GenerateTemporaryTargetAssembly>

    <CreateItem Include="$(IntermediateOutputPath)$(TargetFileName)" >
        <Output TaskParameter="Include" ItemName="AssemblyForLocalTypeReference" />
    </CreateItem>

</Target>

我们需要关注这些点:

  1. 生成临时程序集时,会调用一个编译目标(Target),这个编译目标的名称由 _CompileTargetNameForLocalType 这个私有属性来决定;
  2. _CompileTargetNameForLocalType 没有指定时,会设置其默认值为 _CompileTemporaryAssembly 这个编译目标;
  3. _CompileTemporaryAssembly 这个编译目标执行时,仅会执行三个依赖的编译目标,BuildOnlySettingsResolveKeySourceCoreCompile,至于这些依赖目标所依赖的其他编译目标,则会根据新生成的项目文件动态计算。
  4. 生成临时程序集和临时程序集的编译过程并不在同一个编译上下文中,这也是为什么只能通过传递名称 _CompileTargetNameForLocalType 来执行,而不能直接调用这个编译目标或者设置编译目标的依赖。

新生成的临时项目文件相比于原来的项目文件,包含了这些修改:

  1. 添加了第一轮 XAML 编译传递(MarkupCompilePass1)时生成的 .g.cs 文件;
  2. 将所有引用方式收集到的引用全部换成 ReferencePath,这样就可以避免临时项目编译期间再执行一次 ResolveAssemblyReference 编译目标来收集引用,避免降低太多性能。

关于引用换成 ReferencePath 的内容,可以阅读我的另一篇博客了解更多:

在使用 ReferencePath 的情况下,无论是项目引用还是 NuGet 包引用,都会被换成普通的 dll 引用,因为这个时候目标项目都已经编译完成,包含可以被引用的程序集。

以下是我在示例程序中抓取到的临时生成的项目文件的内容,与原始项目文件之间的差异:

    <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
        <PropertyGroup>
            <OutputType>Exe</OutputType>
            <TargetFramework>net48</TargetFramework>
            <UseWPF>true</UseWPF>
            <GenerateTemporaryTargetAssemblyDebuggingInformation>True</GenerateTemporaryTargetAssemblyDebuggingInformation>
        </PropertyGroup>
        <ItemGroup>
            <PackageReference Include="Walterlv.SourceYard.Demo" Version="0.1.0-alpha" />
        </ItemGroup>
++      <ItemGroup>
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\mscorlib.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\PresentationCore.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\PresentationFramework.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Core.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Data.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Drawing.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.IO.Compression.FileSystem.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Numerics.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Runtime.Serialization.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Windows.Controls.Ribbon.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xaml.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.Linq.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationClient.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationClientsideProviders.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationProvider.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationTypes.dll" />
++          <ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\WindowsBase.dll" />
++      </ItemGroup>
++      <ItemGroup>
++          <Compile Include="D:\Developments\Open\Walterlv.Demo\Walterlv.GettingStarted.SourceYard\Walterlv.GettingStarted.SourceYard.Sample\obj\Debug\net48\Demo.g.cs" />
++      </ItemGroup>
    </Project>

你可能已经注意到了我在项目中设置了 GenerateTemporaryTargetAssemblyDebuggingInformation 属性,这个属性可以让 WPF 临时生成的项目文件保留下来,便于进行研究和调试。在前面 GenerateTemporaryTargetAssembly 的源码部分我们已经贴出了这个属性使用的源码,只是前面我们没有说明其用途。

注意,虽然新生成的项目文件中有 PackageReference 来表示包引用,但由于只有 _CompileTargetNameForLocalType 指定的编译目标和相关依赖可以被执行,而 NuGet 包中自动 Import 的部分没有加入到依赖项中,所以实际上包中的 .props.targets 文件都不会被 Import 进来,这可能造成部分 NuGet 包在 WPF 项目中不能正常工作。比如下面这个:

更典型的,就是 SourceYard 项目,这个 Bug 给 SourceYard 造成了不小的困扰:


参考资料


私のブログはで開始されhttps://blog.walterlv.com/、および特色CSDNから解放されますが、それはめったに一度放出されない更新されます。

あなたが任意のブログの内容を理解していない表示された場合は、共有してください。私が建てDOTNET職業技術学院が参加することを歓迎します。

クリエイティブコモンズライセンス

この作品は、ある非営利- -同一条件許諾4.0の国際ライセンス契約クリエイティブ・コモンズのライセンスのために。:、、使用を転載再投稿、しかし陸毅(リンク含むが署名した文書保管してくださいへようこそhttps://walterlv.blog.csdn.net/を、商業目的のために使用してはならない、記事に基づいて作業が同じライセンスを変更してください)リリース。ご質問があれば、してください私に連絡

おすすめ

転載: blog.csdn.net/WPwalter/article/details/100156343