Search code examples
.netvb.netreflectionappdomain

Load a DLL into a different AppDomain and his dependencies


I'm having a bit trouble to load a DLL by reflection in a different AppDomain.

This is my scenario.

  1. I have a DLL called Interfaces.DLL which only contains an interface Definition.
  2. Test.DLL include Interfaces.DLL and define a class called Connector, which implements the interface defined on previous dll.
  3. I have an app which only include Interfaces.dll and needs to load Test.dll using reflection
  4. I call a public method of class connector which returns me the DLL version of the dll file loaded by reflection. After that, I call a web service to check if i have the greater version of the file. If not, I have to unload the dll, remove the file, and download the new file.

The problem is on step 3. When i try to load Test.DLL in a different AppDomain, i get an error because it cannot find Interfaces.Dll in the AppDomain. The message is:

System.IO.FileNotFoundException was unhandled
 FileName=BankInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

So, how can I load 2 different Dll in a AppDomain?

This is my code:

Interface.DLL

Public Interface BankInterface
       Function getDLLVersion() As Double
       'Other methods here
End Interface

Test.DLL

Public Class Connector
    Implements BankInterfaces.BankInterface
    Public Function getDLLVersion() As Double Implements BankInterfaces.BankInterface.getDLLVersion
         Return 2.5
    End Function

MainApplication

Public Sub Main()
    Dim domainSetup As New AppDomainSetup
    domainSetup.ApplicationName = appDomainName
    domainSetup.ApplicationBase = "C:\Users\jferrer.GLOBAL\AppData\Roaming\Enterprise\AppName\DllFiles\"

    Dim LocalAppDomain As AppDomain = AppDomain.CreateDomain("BankDLL" & Guid.NewGuid.ToString.GetHashCode.ToString("x"), Nothing, domainSetup)
    AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf CurrentDomain_AssemblyResolve

    LocalAppDomain.CreateInstanceFrom("C:\Users\jferrer.GLOBAL\AppData\Roaming\Enterprise\AppName\DllFiles\TestDLL.dll", "TestDLL.Connector") 'This line throw the error
    Dim conector As Type = LocalAppDomain.GetType()

    'Irrelevant code here
end sub

Private Function CurrentDomain_AssemblyResolve(ByVal sender As Object, ByVal args As ResolveEventArgs) As Assembly
    Try
        Dim myassembly As Assembly = Assembly.Load(args.Name)
        If Not IsNothing(myassembly) Then
            Return myassembly
        End If
    Catch ex As Exception
    End Try

    Dim parts As String() = args.Name.Split(",")
    Dim myfile As String = "C:\Users\jferrer.GLOBAL\AppData\Roaming\Enterprise\AppName\DllFiles\" & parts(0).Trim() & ".dll"

    Return Assembly.LoadFrom(myfile)
end function

UPDATE:

If I change

AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf CurrentDomain_AssemblyResolve 

to

AddHandler LocalAppDomain.CurrentDomain.AssemblyResolve, AddressOf CurrentDomain_AssemblyResolve

I get a warning from Visual Studio:

Warning 1   Access of shared member, constant member, enum member or nested type through an instance; qualifying expression will not be evaluated.  C:\Proyectos\BANK\BankTest\Form1.vb 49  20  BankTest

And there is no difference between the original behaviour.


Solution

  • Despite I have not applied directly the suggestions of @James Barras, his comments help me to find this solutions. So, thanks for take the time to help me :)

    This is a posible solution to anyone in my situation:

    1. The class you want to deserialize must inheriht from MarshalByRefObject
    2. The objects returned by the methods inside your class must be serializable
    3. Create this class:

      Imports System.Reflection
      
      Public Class Loader
      Inherits MarshalByRefObject
      Private Function CallInternal(dll As String, typename As String, method As String, parameters As Object()) As Object
          Dim a As Assembly = Assembly.LoadFile(dll)
          Dim o As Object = a.CreateInstance(typename)
          Dim t As Type = o.[GetType]()
          Dim m As MethodInfo = t.GetMethod(method)
          Return m.Invoke(o, parameters)
      End Function
      Public Shared Function [Call](dll As String, typename As String, method As String, ParamArray parameters As Object()) As Object
          Dim dom As AppDomain = AppDomain.CreateDomain("MyNewDomain")
          Dim ld As Loader = DirectCast(dom.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, GetType(Loader).FullName), Loader)
          Dim result As Object = ld.CallInternal(dll, typename, method, parameters)
          AppDomain.Unload(dom)
          Return result
      End Function
      End Class
      
    4. Use this code to call a method inside the dll you want to load:

      Loader.Call(pathToDLL, ClasName,MethodName, parameters)
      

    This solution unload the domain after you call any method. So it is not perfect, because if you want to call several methods, you will be penalized on execution time.