1
+ using Microsoft . CLU . Common . Properties ;
2
+ using System ;
3
+ using System . Collections . Generic ;
4
+ using System . IO ;
5
+ using System . Linq ;
6
+ using System . Reflection ;
7
+
8
+ namespace Microsoft . CLU . Common
9
+ {
10
+ /// <summary>
11
+ /// Type used to resolve dependent assemblies.
12
+ /// </summary>
13
+ public class AssemblyResolver : IDisposable
14
+ {
15
+ /// <summary>
16
+ /// Creates an instance of AssemblyResolver.
17
+ /// </summary>
18
+ /// <param name="lookupRootDirPaths">The root directories to start assembly lookup</param>
19
+ /// <param name="recursiveLookUp">Whether to lookup recursively or not</param>
20
+ public AssemblyResolver ( IEnumerable < string > lookupRootDirPaths , bool recursiveLookUp )
21
+ {
22
+ this . _lookupRootDirPaths = new List < string > ( ) ;
23
+ this . _lookupRootDirPaths . Add ( CLUEnvironment . GetRootPath ( ) ) ;
24
+ this . _lookupRootDirPaths . AddRange ( lookupRootDirPaths ) ;
25
+
26
+ this . _recursiveLookUp = recursiveLookUp ;
27
+
28
+ #if DESKTOPCLR
29
+ _handler = new ResolveEventHandler ( ResolveAssemblyHandler ) ;
30
+ AppDomain . CurrentDomain . AssemblyResolve += _handler ;
31
+ #else
32
+ this . _directoryAssemblyLoadContext = new DirectoryAssemblyLoadContext ( this . _lookupRootDirPaths . ToArray ( ) ) ;
33
+ #endif
34
+ }
35
+
36
+ /// <summary>
37
+ /// String representing the directories searched.
38
+ /// </summary>
39
+ public string SearchDirectoriesMessage
40
+ {
41
+ get
42
+ {
43
+ if ( _lookupRootDirPaths . Count ( ) == 0 )
44
+ {
45
+ return null ;
46
+ }
47
+
48
+ var dirsAsString = ( _recursiveLookUp ? Strings . AssemblyResolver_SearchDirectoriesMessage_TxtIncludingSubDirs : null ) + string . Join ( "," , _lookupRootDirPaths ) ;
49
+ return dirsAsString ;
50
+ }
51
+ }
52
+
53
+
54
+ /// <summary>
55
+ /// Resolve and load an assembly. The assembly will be searched only under
56
+ /// the given collection of directories.
57
+ /// </summary>
58
+ /// <param name="searchDirs">The directories to search</param>
59
+ /// <param name="assemblyName">Relative path or name of the assembly file, with extension</param>
60
+ /// <returns>Returns path to assembly if found else null</returns>
61
+ internal string Resolve ( string [ ] searchDirs , string assemblyName )
62
+ {
63
+ return LookUpFilePath ( searchDirs , assemblyName ) ;
64
+ }
65
+
66
+ /// <summary>
67
+ /// Load the assembly indicated by filePath.
68
+ /// </summary>
69
+ /// <param name="filePath"></param>
70
+ /// <returns></returns>
71
+ internal Assembly Load ( string filePath )
72
+ {
73
+ if ( filePath == null )
74
+ {
75
+ return null ;
76
+ }
77
+ else
78
+ {
79
+ #if DESKTOPCLR
80
+ return Assembly . LoadFile ( filePath ) ;
81
+ #else
82
+ return DirectoryAssemblyLoadContext . LoadFromPath ( filePath ) ;
83
+ #endif
84
+ }
85
+ }
86
+
87
+ /// <summary>
88
+ /// Resolves an entry point given a package assembly.
89
+ /// </summary>
90
+ /// <param name="assembly">The package assembly, already loaded.</param>
91
+ /// <param name="entryName">The fully qualified entry point name.</param>
92
+ /// <returns>A reference to the method information, if it exists.</returns>
93
+ public MethodInfo ResolveEntryPoint ( Assembly assembly , string entryName )
94
+ {
95
+ if ( assembly == null )
96
+ throw new ArgumentNullException ( "assembly" ) ;
97
+ if ( string . IsNullOrEmpty ( entryName ) )
98
+ throw new ArgumentNullException ( "entryName" ) ;
99
+
100
+ var methodNameIdx = entryName . LastIndexOf ( '.' ) ;
101
+ if ( methodNameIdx == - 1 )
102
+ throw new ArgumentException ( Strings . AssemblyResolver_ResolveEntryPoint_MethodNameMustBeFullyQualified ) ;
103
+
104
+ var methodName = entryName . Substring ( methodNameIdx + 1 ) ;
105
+ var className = entryName . Substring ( 0 , methodNameIdx ) ;
106
+
107
+ var clss = assembly . GetType ( className ) ;
108
+ if ( clss != null )
109
+ {
110
+ return clss . GetMethods ( ) . Where ( m => m . Name == methodName ) . FirstOrDefault ( ) ;
111
+ }
112
+ return null ;
113
+ }
114
+
115
+ /// <summary>
116
+ /// Resolves an entry point given a set of package assemblies.
117
+ /// </summary>
118
+ /// <param name="assemblies">The package assemblies, already loaded.</param>
119
+ /// <param name="entryName">The fully qualified entry point name.</param>
120
+ /// <returns>A reference to the method information, if it exists.</returns>
121
+ public MethodInfo ResolveEntryPoint ( IEnumerable < Assembly > assemblies , string entryName )
122
+ {
123
+ return assemblies . Select ( asm => ResolveEntryPoint ( asm , entryName ) ) . FirstOrDefault ( ) ;
124
+ }
125
+
126
+ #if DESKTOPCLR
127
+ private Assembly ResolveAssemblyHandler ( object sender , ResolveEventArgs resolveEventArgs )
128
+ {
129
+ var path = LookUpFilePath ( _lookupRootDirPaths , new AssemblyName ( resolveEventArgs . Name ) . Name + ".dll" ) ;
130
+ return Load ( path ) ;
131
+ }
132
+ #endif
133
+ /// <summary>
134
+ /// Search for an assembly under the given set of directories.
135
+ /// </summary>
136
+ /// <param name="dirPaths">The locations to be searched for the assembly</param>
137
+ /// <param name="assemblyName">Relative path or name of the assembly file, with extension</param>
138
+ /// <returns>Returns the path to the assembly if found else null</returns>
139
+ private string LookUpFilePath ( IEnumerable < string > dirPaths , string assemblyName )
140
+ {
141
+ foreach ( var dirPath in dirPaths )
142
+ {
143
+ string assemblyFilePath = Path . Combine ( dirPath , assemblyName ) ;
144
+ if ( File . Exists ( assemblyFilePath ) )
145
+ {
146
+ return assemblyFilePath ;
147
+ }
148
+
149
+ if ( _recursiveLookUp )
150
+ {
151
+ var subDirPaths = new DirectoryInfo ( dirPath ) . GetDirectories ( ) . Select ( d => d . FullName ) ;
152
+ assemblyFilePath = LookUpFilePath ( subDirPaths , assemblyName ) ;
153
+ if ( assemblyFilePath != null )
154
+ {
155
+ return assemblyFilePath ;
156
+ }
157
+ }
158
+ }
159
+
160
+ return null ;
161
+ }
162
+
163
+ public void Dispose ( )
164
+ {
165
+ Dispose ( true ) ;
166
+ GC . SuppressFinalize ( this ) ;
167
+ }
168
+
169
+ protected virtual void Dispose ( bool disposing )
170
+ {
171
+ if ( ! this . _disposed )
172
+ {
173
+ #if DESKTOPCLR
174
+ if ( disposing )
175
+ {
176
+ if ( _handler != null )
177
+ {
178
+ AppDomain . CurrentDomain . AssemblyResolve -= _handler ;
179
+ _handler = null ;
180
+ }
181
+ }
182
+ #else
183
+ this . _directoryAssemblyLoadContext = null ;
184
+ #endif
185
+ this . _disposed = true ;
186
+ }
187
+ }
188
+
189
+ ~ AssemblyResolver ( )
190
+ {
191
+ Dispose ( false ) ;
192
+ }
193
+
194
+ #region Private fields
195
+
196
+ /// <summary>
197
+ /// The root directories to start lookup.
198
+ /// </summary>
199
+ private List < string > _lookupRootDirPaths ;
200
+
201
+ /// <summary>
202
+ /// Indicate whether to perform recursive lookup or not.
203
+ /// </summary>
204
+ private bool _recursiveLookUp ;
205
+
206
+ #if DESKTOPCLR
207
+ /// <summary>
208
+ /// The assembly resolve event handler.
209
+ /// </summary>
210
+ private ResolveEventHandler _handler ;
211
+ #else
212
+ private DirectoryAssemblyLoadContext _directoryAssemblyLoadContext ;
213
+ #endif
214
+ /// <summary>
215
+ /// used by IDisposible::Dispose.
216
+ /// </summary>
217
+ private bool _disposed = false ;
218
+
219
+ #endregion
220
+
221
+ #if ! DESKTOPCLR
222
+ public class DirectoryAssemblyLoadContext : System . Runtime . Loader . AssemblyLoadContext
223
+ {
224
+ private static Dictionary < string , Assembly > s_loadedAssemblies = new Dictionary < string , Assembly > ( ) ;
225
+ private string [ ] lookupDirPaths ;
226
+
227
+ public DirectoryAssemblyLoadContext ( string [ ] lookupDirPaths )
228
+ {
229
+ this . lookupDirPaths = lookupDirPaths ;
230
+ }
231
+
232
+ public static Assembly LoadFromPath ( string path )
233
+ {
234
+ var resolver = new DirectoryAssemblyLoadContext ( new string [ ] { System . IO . Path . GetDirectoryName ( path ) } ) ;
235
+ var an = new System . Reflection . AssemblyName ( System . IO . Path . GetFileNameWithoutExtension ( path ) ) ;
236
+ return resolver . LoadFromAssemblyName ( an ) ;
237
+ }
238
+
239
+ protected override Assembly Load ( AssemblyName assemblyName )
240
+ {
241
+ //Console.WriteLine($"Trying to load assembly {assemblyName.FullName}");
242
+ Assembly loadedAssembly = null ;
243
+
244
+ if ( s_loadedAssemblies . TryGetValue ( assemblyName . Name , out loadedAssembly ) )
245
+ {
246
+ //Console.WriteLine($"Assembly already dynamically loaded - returning instance...");
247
+ return loadedAssembly ;
248
+ }
249
+
250
+ //Console.WriteLine("Looking up...");
251
+ foreach ( var lookupDirPath in lookupDirPaths )
252
+ {
253
+ //Console.WriteLine($"Searching path {lookupDirPath}");
254
+ foreach ( var file in System . IO . Directory . EnumerateFiles ( lookupDirPath , assemblyName . Name + ".dll" , System . IO . SearchOption . AllDirectories ) )
255
+ {
256
+ //Console.WriteLine($"Loading from file {file}");
257
+ loadedAssembly = LoadFromAssemblyPath ( file ) ;
258
+ }
259
+ }
260
+
261
+ if ( loadedAssembly == null )
262
+ {
263
+ //Console.WriteLine($"Falling back to default loader...");
264
+ loadedAssembly = System . Runtime . Loader . AssemblyLoadContext . Default . LoadFromAssemblyName ( assemblyName ) ;
265
+ }
266
+
267
+ if ( loadedAssembly != null )
268
+ {
269
+ //Console.WriteLine($"Caching loaded assembly...");
270
+ s_loadedAssemblies [ loadedAssembly . GetName ( ) . Name ] = loadedAssembly ;
271
+ }
272
+
273
+ return loadedAssembly ;
274
+ }
275
+ }
276
+ #endif
277
+ }
278
+ }
0 commit comments