10000 Add RetargetablePathAssemblyResolver · vincent-coder0119/wpf@99d9347 · GitHub
[go: up one dir, main page]

Skip to content

Commit 99d9347

Browse files
committed
Add RetargetablePathAssemblyResolver
1 parent 05ce539 commit 99d9347

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

src/Microsoft.DotNet.Wpf/src/PresentationBuildTasks/PresentationBuildTasks.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@
9898
<Compile Include="$(WpfSharedDir)\System\Windows\Markup\ReflectionHelper.cs">
9999
<Link>Shared\System\Windows\Markup\ReflectionHelper.cs</Link>
100100
</Compile>
101+
<Compile Include="$(WpfSharedDir)\System\Windows\Markup\RetargetablePathAssemblyResolver.cs">
102+
<Link>Shared\System\Windows\Markup\RetargetablePathAssemblyResolver.cs</Link>
103+
</Compile>
101104
<Compile Include="$(WpfSharedDir)\MS\Internal\Xaml\Parser\SpecialBracketCharacters.cs">
102105
<Link>Shared\MS\Internal\Xaml\Parser\SpecialBracketCharacters.cs</Link>
103106
</Compile>

src/Microsoft.DotNet.Wpf/src/Shared/System/Windows/Markup/ReflectionHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ internal static void Initialize(IEnumerable<string> assemblyPaths)
6262
// System.Reflection.MetadataLoadContext Assembly cache
6363
_cachedMetadataLoadContextAssemblies = new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
6464
_cachedMetadataLoadContextAssembliesByNameNoExtension = new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase);
65-
_metadataLoadContext = new MetadataLoadContext(new PathAssemblyResolver(assemblyPaths), MscorlibReflectionAssemblyName);
65+
_metadataLoadContext = new MetadataLoadContext(new RetargetablePathAssemblyResolver(assemblyPaths), MscorlibReflectionAssemblyName);
6666
_localAssemblyName = string.Empty;
6767
}
6868

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Generic;
6+
using System.IO;
7+
8+
namespace System.Reflection
9+
{
10+
/// <summary>
11+
/// An assembly resolver that uses paths to every assembly that may be loaded.
12+
/// The file name is expected to be the same as the assembly's simple name.
13+
/// Multiple assemblies can exist on disk with the same name but in different directories.
14+
/// A single instance of RetargetablePathAssemblyResolver can be used with multiple MetadataAssemblyResolver instances.
15+
/// </summary>
16+
/// <remarks>
17+
/// In order for an AssemblyName to match to a loaded assembly, AssemblyName.Name must be equal (casing ignored).
18+
/// - If AssemblyName.PublicKeyToken is specified, it must be equal.
19+
/// - If AssemblyName.PublicKeyToken is not specified, assemblies with no PublicKeyToken are selected over those with a PublicKeyToken.
20+
/// - If more than one assembly matches, the assembly with the highest Version is returned.
21+
/// - CultureName is ignored.
22+
/// </remarks>
23+
public class RetargetablePathAssemblyResolver : MetadataAssemblyResolver
24+
{
25+
private readonly Dictionary<string, List<string>> _fileToPaths = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="System.Reflection.RetargetablePathAssemblyResolver"/> class.
29+
/// </summary>
30+
/// <exception cref="System.ArgumentNullException">Thrown when assemblyPaths is null.</exception>
31+
/// <exception cref="System.ArgumentException">Thrown when a path is invalid.</exception>
32+
public RetargetablePathAssemblyResolver(IEnumerable<string> assemblyPaths)
33+
{
34+
if (assemblyPaths == null)
35+
throw new ArgumentNullException(nameof(assemblyPaths));
36+
37+
foreach (string path in assemblyPaths)
38+
{
39+
if (string.IsNullOrEmpty(path))
40+
throw new ArgumentException(path);
41+
42+
string file = Path.GetFileNameWithoutExtension(path);
43+
44+
if (file.Length == 0)
45+
throw new ArgumentException(path);
46+
47+
List<string> paths;
48+
if (!_fileToPaths.TryGetValue(file, out paths))
49+
{
50+
_fileToPaths.Add(file, paths = new List<string>());
51+
}
52+
paths.Add(path);
53+
}
54+
}
55+
56+
public override Assembly Resolve(MetadataLoadContext context, AssemblyName assemblyName)
57+
{
58+
Assembly candidateWithSamePkt = null;
59+
Assembly candidateIgnoringPkt = null;
60+
if (_fileToPaths.TryGetValue(assemblyName.Name, out List<string> paths))
61+
{
62+
ReadOnlySpan<byte> pktFromName = assemblyName.GetPublicKeyToken();
63+
bool isRetargetableAssembly = ((assemblyName.Flags & AssemblyNameFlags.Retargetable) != 0);
64+
65+
foreach (string path in paths)
66+
{
67+
Assembly assemblyFromPath = context.LoadFromAssemblyPath(path);
68+
AssemblyName assemblyNameFromPath = assemblyFromPath.GetName();
69+
if (assemblyName.Name.Equals(assemblyNameFromPath.Name, StringComparison.OrdinalIgnoreCase))
70+
{
71+
ReadOnlySpan<byte> pktFromAssembly = assemblyNameFromPath.GetPublicKeyToken();
72+
73+
// Find exact match on PublicKeyToken including treating no PublicKeyToken as its own entry.
74+
if (pktFromName.SequenceEqual(pktFromAssembly))
75+
{
76+
// Pick the highest version.
77+
if (candidateWithSamePkt == null || assemblyNameFromPath.Version > candidateWithSamePkt.GetName().Version)
78+
{
79+
candidateWithSamePkt = assemblyFromPath;
80+
}
81+
}
82+
// If assemblyName does not specify a PublicKeyToken, or assemblyName is marked 'Retargetable',
83+
// then still consider those with a PublicKeyToken, and take the highest version available.
84+
else if (candidateWithSamePkt == null && (pktFromName.IsEmpty || isRetargetableAssembly))
85+
{
86+
// Pick the highest version.
87+
if (candidateIgnoringPkt == null || assemblyNameFromPath.Version > candidateIgnoringPkt.GetName().Version)
88+
{
89+
candidateIgnoringPkt = assemblyFromPath;
90+
}
91+
}
92+
}
93+
}
94+
}
95+
96+
return candidateWithSamePkt ?? candidateIgnoringPkt;
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)
0