0

I'm writing a library that, given an assembly, produces a list of it's dependency assemblies - including dependencies' dependencies. The intended usage scenario includes mostly managed code (.NET) assemblies.

My approach to computing this dependency list is the following:

  • Create a temporary AppDomain (code)
  • Create an instance of a helper class in that AppDomain (code)
  • Feed that helper my initial assembly: the root of the dependency tree (code)
  • Load that assembly for inspection in the temporary AppDomain's reflection-only context (code)
  • Go after dependencies recursively

Please observe that this helper class is defined in an assembly that only references .NET assemblies; nothing else. The main library's assembly does have references to System.Reactive assemblies.

I would expect the temporary AppDomain to not have loaded these System.Reactive assemblies, but I'm getting an error that suggests otherwise.

API restriction: The assembly 'file:///X:\some-path\System.Reactive.Linq.dll' has already loaded from a different location. It cannot be loaded from a new location within the same appdomain.
   at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, Boolean suppressSecurityChecks, StackCrawlMark& stackMark)
   at System.Reflection.Assembly.ReflectionOnlyLoadFrom(String assemblyFile)
   at DependencyResolver.AppDomainHelper.Util.AssemblyLoader.ReflectionOnlyLoadFromAssemblyPath(AssemblyName assembly, FileNotFoundException originalException) in DependencyResolver\DependencyResolver.AppDomainHelper\Util\AssemblyLoader.cs:line 96
   at DependencyResolver.AppDomainHelper.Util.AssemblyLoader.ReflectionOnlyLoad(AssemblyName assembly) in DependencyResolver\DependencyResolver.AppDomainHelper\Util\AssemblyLoader.cs:line 67
   at DependencyResolver.AppDomainHelper.Util.AssemblyLoader.ReflectionOnlyLoad(AssemblyName assembly)
   at DependencyResolver.Resolver.GetAllDependenciesRecursive(AssemblyName start, Func`2 filter, Func`2 preProcess, Action`1 postProcess) in DependencyResolver\DependencyResolver\Resolver.cs:line 39
   at DependencyResolver.Resolver.<>c__DisplayClass6.<GetAllDependenciesRecursive>b__5(AssemblyName an) in DependencyResolver\DependencyResolver\Resolver.cs:line 51
   at System.Reactive.Linq.Observαble.Select`2._.OnNext(TSource value)

This happens when I use the library on a pile of assemblies that contains a different version of System.Reactive.Linq.dll.

How can I make sure my temporary AppDomain will not load anything it doesn't need?

derabbink
  • 2,419
  • 1
  • 22
  • 47
  • Creating your own little corner of DLL Hell is pretty much guaranteed when you do this. Stop helping so much, just make sure the probing paths are set correctly. – Hans Passant Sep 18 '13 at 15:01
  • That's not so helpful. I need a library like this for another project where I am transporting only the necessary assemblies (to execute one particular method) out of a huge set of DLLs. – derabbink Sep 18 '13 at 16:29

1 Answers1

0

One way would be to subscribe to the AssemblyResolve event of the AppDomain and execute the method? This way when an assembly cannot be resolved, the event handler will be called to load the assembly. The only problem here is when you have assemblies with the same filename but different strong names.

An alternative approach would be to have all of your assemblies in the base directory of the AppDomain and then simply call the method. Afterwards inspect the loaded assemblies on the AppDomain. This should give you the list you are looking for, given that the CLR will load each assembly lazily. You can use the private bin paths whenever you have assemblies with the same filename but different strong names.

Note that you will have a problem if you have "weak-named" assemblies with the same filename.

Panos Rontogiannis
  • 4,154
  • 1
  • 24
  • 29
  • I think you misunderstood my question. I have no problem finding potentially relevant dependency-DLLs. My problem seems to be that a new AppDomain I create is supposed to be empty (i.e. it has not loaded anything), but instead it seems like it *did* load some assemblies. – derabbink Sep 19 '13 at 04:51
  • @derabbink I see. Will have a look at it and let you know. Do you have a test that reproduces this scenario? By the way your GetAllTestDependencies test should compare sorted lists and you should add the DependencyResolver.AppDomainHelper to the expectedNames list. – Panos Rontogiannis Sep 19 '13 at 08:25
  • I tried to write a [test case](https://github.com/derabbink/DependencyResolver/blob/master/DependsOnOldRx.Test/DependencyResolver.cs#L15), but it does not reproduce the exception I got in the first place. The scenario I had when the exception originally appeared involves a lot of proprietary code so I cannot plainly publish it here. I'll keep searching for a reproduceable example – derabbink Sep 23 '13 at 10:59