Making shipped assemblies: interacting between domains without marshalling



    Link to a project on GitHub: DotNetEx

    On multiple resources, a question is asked from time to time. Is it possible to make shipped assemblies from the current domain? So to take advantage of and “come on, goodbye!”? Everywhere and always the answer that was given is "no." After all, the only thing you can unload is a domain. Accordingly, if you want to arrange shipping, the assembly must be placed in the domain, and communication between the domains through serializable types must be established. And this is a very slow interaction. And we will say so. Can. With the nuances. We will also upload to a separate domain. But we will cancel serialization when calling methods between domains .

    Questions that we will solve:
    • Creating a domain with the ability to return the object from the domain to the parent
    • Assembly unloading


    Solution


    So, as always, we will solve problems as they appear:
    • As we already found in previous articles, the memory is shared and independent of domains. And this means that if you find a way to transfer a pointer to an object, you can learn how to transfer objects between domains without serialization.

      Let's take some general type. For simplicity, let's take a type from mscorlib: IServiceProvider.
      Let's create the assembly that we are going to make friends with the possibility of shipment:
      public class Implementation : IServiceProvider
          {
              public override string ToString()
              {
                  return "Awesome";
              }
              public object GetService(Type serviceType)
              {
                  return new object();
              }
          }
      

    • Now we’ll write a class that will create the domain and be able to create class instances in this domain:
          public class AppDomainRunner : MarshalByRefObject, IDisposable
          {
              private AppDomain appDomain;
              private Assembly assembly;
              private AppDomainRunner remoteRunner;
              private void LoadAssembly(string assemblyPath)
              {
                  assembly = Assembly.LoadFile(assemblyPath);
              }
              public AppDomainRunner(string assemblyPath)
              {
                  // make appdomain
                  appDomain = AppDomain.CreateDomain("PseudoIsolated", null, 
                      new AppDomainSetup
                      {
                          ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
                      });
                  // create object instance
                  remoteRunner = (AppDomainRunner)appDomain.CreateInstanceAndUnwrap(typeof(AppDomainRunner).Assembly.FullName, typeof(AppDomainRunner).FullName);
                  remoteRunner.LoadAssembly(assemblyPath);
              }
              public IntPtr CreateInstance(string typename)
              {
                  return remoteRunner.CreateInstanceImpl(typename);
              }
              private IntPtr CreateInstanceImpl(string typename)
              {
                  return EntityPtr.ToPointer(assembly.CreateInstance(typename));
              }
              public void Dispose()
              {
                  assembly = null;
                  remoteRunner = null;
                  AppDomain.Unload(appDomain);
              }
      
    • Now let's write a class for the IoC container:
          public class Container : IDisposable
          {
              private AppDomainRunner appdomain;
              private Dictionary instances = new Dictionary(); 
              public Container(string assemblyName)
              {
                  appdomain = new AppDomainRunner(Path.Combine(System.Environment.CurrentDirectory, assemblyName));
              }
              public void Register(string fullTypeName)
              {
                  instances.Add(typeof (TInterface), EntityPtr.ToInstance(appdomain.CreateInstance(fullTypeName)));
              }
              public TInterface Resolve()
              {
                  return (TInterface)(instances[typeof (TInterface)]);
              }
              public void Dispose()
              {
                  appdomain.Dispose();
              }
          }
      
      И последнее – использующий код:
              static void Main(string[] args)
              {
                  using (var container = new Container("library.dll"))
                  {
                      container.Register("IocLibrary.Implementation");
                      var serviceProvider = container.Resolve();
                      Console.WriteLine("calling method without proxy: {0}", serviceProvider);
                      Console.WriteLine("Current domain assemblies: {0}",
                                        string.Join(",  ", AppDomain.CurrentDomain.GetAssemblies().Select(asm => asm.GetName().Name).ToArray()));
                  }
              }
      



      Выводы


      Как говорится, сделать отгружаемые типы нельзя, но если хочется, то можно. Необходимо просто как-то передать между доменами указатель на объект и им можно будет на равных правах пользоваться.
      Минус способа только один: мы не имеем права использовать объекты после выгрузки сборки. Это минус по одной простой причине: надо дополнительно контролировать порядок отгрузки и потери ссылки на объекты. Но, в общем случае это не проблема =)

    Also popular now: