-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathAssemblyLoader.cs
130 lines (109 loc) · 5.37 KB
/
AssemblyLoader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace AssemblyLoader
{
public class AssemblyLoader
{
private List<FileInfo> assembliesToLoad = new List<FileInfo>();
private List<(string dllName, Version version, FileInfo fileInfo)> dependencyPool = new List<(string dllName, Version version, FileInfo fileInfo)>();
public bool ShareDependenciesWithHost { get; set; } = true;
public void Use(FileInfo assemblyFile)
{
this.assembliesToLoad.Add(assemblyFile);
var dependencies = ScanDependencies(assemblyFile);
this.dependencyPool.AddRange(dependencies);
}
private static List<(string dllName, Version version, FileInfo fileInfo)> ScanDependencies(FileInfo assemblyFile)
{
var dependencies = assemblyFile.Directory
.GetFiles("*.dll")
.Where(fileInfo => fileInfo != assemblyFile)
.Select(dependency => InspectDll(dependency))
.ToList();
return dependencies;
}
private static (string dllName, Version version, FileInfo fileInfo) InspectDll(FileInfo assemblyFile)
{
Version version;
try
{
// Try to retrieve File Version, as it's usually more up-to-date than Assembly Version
var fileVersionInfo = FileVersionInfo.GetVersionInfo(assemblyFile.FullName).FileVersion;
var fileVersion = Version.Parse(fileVersionInfo);
version = fileVersion;
}
catch
{
// Fall back to provide Assembly Version
var assemblyName = AssemblyName.GetAssemblyName(assemblyFile.FullName);
var assemblyVersion = assemblyName.Version;
version = assemblyVersion;
}
var assemblyFileNameWithoutExtension = assemblyFile.Name.Replace(assemblyFile.Extension, "");
return (assemblyFileNameWithoutExtension, version, assemblyFile);
}
public Assembly[] Load()
{
// Deduplicate the pool by eliminating duplicates and only keeping the latest versions
var mergedPool = dependencyPool
.GroupBy(assemblyInfo => assemblyInfo.dllName)
.Select(group => group
.OrderByDescending(assemblyInfo => assemblyInfo.version)
.First())
.ToList();
var assemblyLoader = new DirectoryLoader(mergedPool, this.ShareDependenciesWithHost);
var loadedAssemblies = this.assembliesToLoad
.Select(assemblyFile => assemblyLoader.LoadFromAssemblyPath(assemblyFile.FullName))
.ToArray();
return loadedAssemblies;
}
private class DirectoryLoader : AssemblyLoadContext
{
public static Assembly[] assembliesLoadedAtStartup;
private readonly List<(string dllName, Version version, FileInfo fileInfo)> dependencyPool;
private readonly List<Assembly> loadedDependencies;
private readonly bool shareDependenciesWithHost;
public DirectoryLoader(List<(string dllName, Version version, FileInfo fileInfo)> dependencyPool, bool shareDependenciesWithHost)
{
this.dependencyPool = dependencyPool;
this.loadedDependencies = new List<Assembly>();
this.shareDependenciesWithHost = shareDependenciesWithHost;
// 'assembliesLoadedAtStartup' is a static field,
// we populate it the first time we get here,
// just before we would start loading new assemblies
if (assembliesLoadedAtStartup == null)
assembliesLoadedAtStartup = AppDomain.CurrentDomain.GetAssemblies();
}
protected override Assembly Load(AssemblyName assemblyName)
{
if (this.shareDependenciesWithHost)
{
// Try to use an assembly that is already loaded (no strong version match is forced)
var existingSameOrNewerVersionAssembly = assembliesLoadedAtStartup.SingleOrDefault(a => a.GetName().Name == assemblyName.Name);
if (existingSameOrNewerVersionAssembly != null) return existingSameOrNewerVersionAssembly;
}
try
{
// Try to use the default assembly loader
return Default.LoadFromAssemblyName(assemblyName);
}
catch
{
// Try to use an already loaded assembly from the current context
var existingAssembly = this.loadedDependencies.SingleOrDefault(a => a.FullName == assemblyName.FullName);
if (existingAssembly != null)
return existingAssembly;
// Fall back to load assembly from known directory using the "DependencyPool"
var loadedAssembly = LoadFromAssemblyPath(this.dependencyPool.Single(x => x.dllName == assemblyName.Name).fileInfo.FullName);
this.loadedDependencies.Add(loadedAssembly);
return loadedAssembly;
}
}
}
}
}