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を持っている、
MainResourcesGeneration
とSatelliteResourceGeneration
主なリソースの生成やリソース世代の子会社を担当しました。 - タスク名:
ResourcesGenerator
1つまたは複数のリソース(バイナリ形式.JPG、.ICO、.BMP、XAML、および拡張の他のタイプ)埋め込み.resourcesファイルのファイル。
CheckUid、UpdateUid、RemoveUid
- ターゲットは3を持っている
CheckUid
、UpdateUid
とRemoveUid
主なリソースの生成やリソース世代の子会社を担当しました。 - タスク名:
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いくつかの重要なターゲット上に列挙したものを対象に、時間をコンパイルターゲットコンパイラの実用化には多くのがあるでしょう。また、通常のコンパイル処理ではなく、専用ビルドプロセスで実行されるいくつかではありません。
地図の読み込み方法はこれです:
- 箭头代表依赖关系,如
CoreCompile
有一个指向DesignTimeMarkupCompilation
的箭头,表示CoreCompile
执行前会确保DesignTimeMarkupCompilation
执行完毕; - 如果一个 Target 有多个依赖,则这些依赖会按顺序执行还没执行的依赖,如
PrepareResources
指向了多个 TargetMarkupCompilePass1
、GenerateTemporaryTargetAssembly
、MarkupCompilePass2
、AfterMarkupCompilePass2
、CleanupTemporaryTargetAssembly
,那么在PrepareResources
执行之前,如果还有没有执行的依赖,会按顺序依次执行; - 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 编译过程有两个编译传递,MarkupCompilePass1
和 MarkupCompilePass2
。
MarkupCompilePass1
的作用是将 XAML 编译成二进制格式。如果 XAML 文件包含 x:Class
属性,那么就会根据语言生成一份代码文件;对于 C# 语言,会生成“文件名.g.cs”文件。但是 XAML 文件中也有可能包含对同一个程序集中的 CLR 类型的引用,然而这一编译阶段 CLR 类型还没有开始编译,因此无法提供程序集引用。所以如果这个 XAML 文件包含对同一个程序集中 CLR 类型的引用,则这个编译会被推迟到 MarkupCompilePass2
中继续。而在 MarkupCompilePass1
和 MarkupCompilePass2
之间,则插入了 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>
我们需要关注这些点:
- 生成临时程序集时,会调用一个编译目标(Target),这个编译目标的名称由
_CompileTargetNameForLocalType
这个私有属性来决定; - 当
_CompileTargetNameForLocalType
没有指定时,会设置其默认值为_CompileTemporaryAssembly
这个编译目标; _CompileTemporaryAssembly
这个编译目标执行时,仅会执行三个依赖的编译目标,BuildOnlySettings
、ResolveKeySource
、CoreCompile
,至于这些依赖目标所依赖的其他编译目标,则会根据新生成的项目文件动态计算。- 生成临时程序集和临时程序集的编译过程并不在同一个编译上下文中,这也是为什么只能通过传递名称
_CompileTargetNameForLocalType
来执行,而不能直接调用这个编译目标或者设置编译目标的依赖。
新生成的临时项目文件相比于原来的项目文件,包含了这些修改:
- 添加了第一轮 XAML 编译传递(
MarkupCompilePass1
)时生成的 .g.cs 文件; - 将所有引用方式收集到的引用全部换成
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 造成了不小的困扰:
参考资料
- WPF MSBuild Task Reference - Visual Studio - Microsoft Docs
- GenerateTemporaryTargetAssembly.cs
- Localization Attributes and Comments - Microsoft Docs
私のブログはで開始されhttps://blog.walterlv.com/、および特色CSDNから解放されますが、それはめったに一度放出されない更新されます。
あなたが任意のブログの内容を理解していない表示された場合は、共有してください。私が建てDOTNET職業技術学院が参加することを歓迎します。
この作品は、ある非営利- -同一条件許諾4.0の国際ライセンス契約クリエイティブ・コモンズのライセンスのために。:、、使用を転載再投稿、しかし陸毅(リンク含むが署名した文書保管してくださいへようこそhttps://walterlv.blog.csdn.net/を、商業目的のために使用してはならない、記事に基づいて作業が同じライセンスを変更してください)リリース。ご質問があれば、してください私に連絡。