开发者

Use AppDomain to load/unload external assemblies

My scenario is as follows:

  • Create new AppDomain
  • Load some assemblies into it
  • Do some magic with loaded dlls
  • Unload AppDomain to release memory & loaded libraries

Below is the code that I'm trying to use

    class Program
{
    static void Main(string[] args)
    {
        Evidence e = new Evidence(AppDomain.CurrentDomain.Evidence);
        AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
        Console.WriteLine("Creating new AppDomain");
        AppDomain newDomain = AppDomain.CreateDomain("newDomain", e, setup);
        string fullName = Assembly.GetExecutingAssembly().FullName;
        Type loaderType = typeof(AssemblyLoader);
        var loader = (AssemblyLoader)newDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();
        Console.WriteLine("Loading assembly");
        Assembly asm = loader.LoadAssembly("library.dll");
        Console.WriteLine("Creating instance of Class1");
        object instance = Activator.CreateInstance(asm.GetTypes()[0]);
        Console.WriteLine("Created object is of type {0}", instance.GetType());
        Console.ReadLine();
        Console.WriteLine("Unloading AppDomain");
        instance = null;
        AppDomain.Unload(newDomain);
        Console.WriteLine("New Domain unloaded");
        Console.ReadLine();
    }

    public class AssemblyLoader : MarshalByRefObject
    {
        public Assembly LoadAssembly(string path)
        {
            return Assembly.LoadFile(path);
        }
    }
}

library.dll consists only of a single dummy class, with one huge string table(for easier tracking the me开发者_如何学JAVAmory consumption)

Now the problem is that memory actually isn't freed. What's more surprising, memory usage actually increases after AppDomain.Unload()

Can anyone shed some light on this issue?


This is not a complete answer: I just noticed that you use a string as payload. Strings are not useful for this, as literal strings are interned. The interned strings are shared among AppDomains, so that part is not unloaded when you unload your AppDomain. Try using a byte[] instead.


Answering my own question - don't know if there's better way to do it on StackOverflow... If there is, I'd be grateful for instructions... Anyway, digging through internet I found another solution, which I hope is better. Code below, if anyone finds any weak points - please respond.

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();
        for(int i=0;i<10;i++)
        {
            AppDomain appDomain = AppDomain.CreateDomain("MyTemp");
            appDomain.DoCallBack(loadAssembly);
            appDomain.DomainUnload += appDomain_DomainUnload;

            AppDomain.Unload(appDomain);        
        }

        AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2");
        appDomain2.DoCallBack(loadAssembly);
        appDomain2.DomainUnload += appDomain_DomainUnload;

        AppDomain.Unload(appDomain2);

        GC.Collect();
        GC.WaitForPendingFinalizers();  
        Console.ReadLine();
    }

    private static void loadAssembly()
    {
        string fullPath = @"E:\tmp\sandbox\AppDomains\AppDomains1\AppDomains1\bin\Debug\BigLib.dll";
        var assembly = Assembly.LoadFrom(fullPath);
        var instance = Activator.CreateInstance(assembly.GetTypes()[0]);
        Console.WriteLine("Creating instance of {0}", instance.GetType());
        Thread.Sleep(2000);
        instance = null;
    }

    private static void appDomain_DomainUnload(object sender, EventArgs e)
    {
        AppDomain ap = sender as AppDomain;
        Console.WriteLine("Unloading {0} AppDomain", ap.FriendlyName);
    }
}


I have posted an example where 3 different assemblies are loaded in different app domains and unloaded successfully. Here is the link http://www.softwareinteractions.com/blog/2010/2/7/loading-and-unloading-net-assemblies.html


This is a late answer but would be worthwhile to have it here for any future views to this question. I needed to implement something similar to this but in a dynamic code compilation/execution fashion. The best would be executing all methods in a separate domain, i.e.: remote domain, other than your main AppDomain, otherwise the app memory will always increase and increase. You can solve this problem via remote interfaces and proxies. So you would expose your methods through an interface which you will get an instance of in your main AppDomain and then remotely execute those methods in the remote domain, unload the newly created domain (remote domain), nullify it and then force the GC to collect unused objects. I spent quite a long time debugging my code until I reliazed that I have to force the GC to do so and it works just fine. Bulk of my implementation is taken from: http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm.

      //pseudo code
      object ExecuteCodeDynamically(string code)
       {
        Create AppDomain my_app
         src_code = "using System; using System.Reflection; using RemoteLoader;
        namespace MyNameSpace{
       public class MyClass:MarshalByRefObject, IRemoteIterface
      {
      public object Invoke(string local_method, object[] parameters)
        {
       return this.GetType().InvokeMember(local_method, BindingFlags.InvokeMethod, null, this,    parameters);
     }
     public object ExecuteDynamicCode(params object[] parameters)
     {
    " + code + } } } ";// this whole big string is the remote application

     //compile this code which is src_code
     //output it as a DLL on the disk rather than in memory with the name e.g.: DynamicHelper.dll. This can be done by playing with the CompileParameters
     // create the factory class in the secondary app-domain
               RemoteLoader.RemoteLoaderFactory factory =
                  (RemoteLoader.RemoteLoaderFactory)loAppDomain.CreateInstance("RemoteLoader",
                  "RemoteLoader.RemoteLoaderFactory").Unwrap();

            // with the help of this factory, we can now create a real instance
            object loObject = factory.CreateInstance("DynamicHelper.dll", "MyNamespace.MyClass", null);

            // *** Cast the object to the remote interface to avoid loading type info
            RemoteLoader.IRemoteInterface loRemote = (RemoteLoader.IRemoteInterface)loObject;

            if (loObject == null)
            {
                System.Windows.Forms.MessageBox.Show("Couldn't load class.");
                return null;
            }

            object[] loCodeParms = new object[1];
            loCodeParms[0] = "bla bla bla";

            try
            {
                // *** Indirectly call the remote interface
                object result = loRemote.Invoke("ExecuteDynamicCode", loCodeParms);// this is the object to return                

            }
            catch (Exception loError)
            {
                System.Windows.Forms.MessageBox.Show(loError.Message, "Compiler Demo",
                    System.Windows.Forms.MessageBoxButtons.OK,
                    System.Windows.Forms.MessageBoxIcon.Information);
                return null;
            }

            loRemote = null;
            try { AppDomain.Unload(my_app); }
            catch (CannotUnloadAppDomainException ex)
            { String str = ex.Message; }
            loAppDomain = null;
            GC.Collect();//this will do the trick and free the memory
            GC.WaitForPendingFinalizers();
            System.IO.File.Delete("ConductorDynamicHelper.dll");
            return result;

}

Note that RemoteLoader is another DLL that should be already created and added to both you main App and your remote App. It's basically an interface and a factory loader. The following code is taken from the above website:

      /// <summary>
    /// Interface that can be run over the remote AppDomain boundary.
   /// </summary>
     public interface IRemoteInterface
     {
    object Invoke(string lcMethod,object[] Parameters);
     }


     naemspace RemoteLoader{
   /// <summary>
   /// Factory class to create objects exposing IRemoteInterface
  /// </summary>
  public class RemoteLoaderFactory : MarshalByRefObject
 {
   private const BindingFlags bfi = BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance;

 public RemoteLoaderFactory() {}

 /// <summary> Factory method to create an instance of the type whose name is specified,
  /// using the named assembly file and the constructor that best matches the specified parameters.  </summary>
 /// <param name="assemblyFile"> The name of a file that contains an assembly where the type named typeName is sought. </param>
 /// <param name="typeName"> The name of the preferred type. </param>
 /// <param name="constructArgs"> An array of arguments that match in number, order, and type the parameters of the constructor to invoke, or null for default constructor. </param>
 /// <returns> The return value is the created object represented as ILiveInterface. </returns>
 public IRemoteInterface Create( string assemblyFile, string typeName, object[] constructArgs )
 {
  return (IRemoteInterface) Activator.CreateInstanceFrom(
  assemblyFile, typeName, false, bfi, null, constructArgs,
  null, null, null ).Unwrap();
   }
  }
  }

Hope this makes sense and helps...


.Net uses non-deterministic finalization. If you want to see if the memory drops you should do ...

GC.Collect(); 
GC.WaitForPendingFinalizers();

... after the unload. Also unless you have a need to force collection (rather un-likely) you should allow the system to collect on its own. Normally if you feel the need to force collection in production code there is a resource leak typically caused by not calling Dispose on IDisposable objects or for not Releasing unmanaged objects

using (var imdisposable = new IDisposable())
{
}
//
var imdisposable = new IDisposable();
imdisposable.Dispose();
//
Marshal.Release(intPtr); 
//
Marshal.ReleaseComObject(comObject);


Each assembly is loaded into the main domain as well. Since you use Assembly instance, your main domain loads this assembly in order to be able to analyze all the types in it.

If you want to prevent loading assembly in both domains - use AppDomain.CreateInstance method.


Actually, combination of above answers pointed me to (I hope) correct answer: My code is now as follows:

AppDomain newDomain = AppDomain.CreateDomain("newDomain", e, setup);
string fullName = Assembly.GetExecutingAssembly().FullName;
Type loaderType = typeof(AssemblyLoader);
FileStream fs = new FileStream(@"library.dll", FileMode.Open);
byte[] buffer = new byte[(int)fs.Length];
fs.Read(buffer, 0, buffer.Length);
fs.Close();

Assembly domainLoaded = newDomain.Load(buffer);
object loaded = Activator.CreateInstance(domainLoaded.GetTypes()[1]);
AppDomain.Unload(newDomain);
GC.Collect();
GC.WaitForPendingFinalizers();

I can't use AppDomain.CreateInstance, since it requires Assembly.FullName which I don't know - library is loaded dynamically.

Thanks for the help, Bolek.


you can try this code: http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜