Author Topic: Load/Unload .net-dll in special AppDomain  (Read 11087 times)

0 Members and 1 Guest are viewing this topic.

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Load/Unload .net-dll in special AppDomain
« on: June 20, 2011, 04:22:11 AM »
I have written the .net-module (dll-file) which can be loaded in AutoCAD by means of command NETLOAD. Has checked up - all works. But I want to have possibility not only to load, but also unload such modules - therefore I want to load them at once in separate AppDomain.
I read MSDN here. Hasn't understood, what sense in AppDomain. Load if it loads the code not only in the domain necessary to me but also in the main:
Quote from: MSDN
If the current AppDomain object represents application domain A, and the Load method is called from application domain B, the assembly is loaded into both application domains. For example, the following code loads MyAssembly into the new application domain ChildDomain and also into the application domain where the code executes:
Code: [Select]
AppDomain ad = AppDomain.CreateDomain("ChildDomain");
    ad.Load("MyAssembly");
?
That the code was loaded only in that module which it is necessary - I have tried to use CrossAppDomainDelegate in my code sample (C# code):
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using acad = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System.Reflection;

[assembly: CommandClass(typeof(DomainUseSample.Class1))]

namespace DomainUseSample
{
    public sealed class Class1
    {
        static AppDomain dom;
        string path = @"D:\MD\Visual Studio 2010\CmdSample\CmdSample\bin\Debug\CmdSample.dll";

        [CommandMethod("bush", "dload", CommandFlags.Session)]
        public void Load()
        {
            if (dom != null)
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage("The domain already exists.\n");
                return;
            }
            dom = AppDomain.CreateDomain("TestDomain");
            try
            {
                                
                dom.DoCallBack(new CrossAppDomainDelegate(LoadDll));
            }
            catch (System.Exception ex)
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("Excetion message: \"{0}\"\n", ex.Message));
                return;
            }
            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage("Successful loading.\n");
        }

        [CommandMethod("bush", "dunload", CommandFlags.Session)]
        public void Unload()
        {
            if (dom == null)
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage("The domain isn't created yet! At first launch a command dload.\n");
                return;
            }            
            try
            {
                AppDomain.Unload(dom);
                dom = null;                
            }
            catch (System.Exception ex)
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("Excetion message: \"{0}\"\n", ex.Message));
                return;
            }
            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage("Successful unloading.\n");
        }

        void LoadDll() {
            Assembly asm = Assembly.LoadFrom(path);
        }        
    }
}
When I load this code and I launch a command dload - I receive an exception:
Quote
Command: netload

Command: dload
Excetion message: "Type 'DomainUseSample.Class1' in assembly 'DomainUseSample,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as
serializable."
Ok, I mark class Class1 with attribute Serializable:
Code: [Select]
   [Serializable]
    public sealed class Class1
    {
       ...
    }
Now, when I load this code and I launch a command dload - I receive an other exception:
Quote
Command: netload

Command: dload
Excetion message: "Type is not resolved for member
'DomainUseSample.Class1,DomainUseSample, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null'."

How correctly to load and unload .net-libraries in separate AppDomain by operation with AutoCAD?
« Last Edit: June 20, 2011, 04:31:58 AM by Andrey »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Load/Unload .net-dll in special AppDomain
« Reply #1 on: June 20, 2011, 05:27:28 AM »
I have changed the code: has declared methods and fields as "static" and as has commented out the code of method LoadDll (in comments I have shown where there is an exception):
Code: [Select]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using acad = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using System.Reflection;

[assembly: CommandClass(typeof(DomainUseSample.Class1))]

namespace DomainUseSample
{
    [Serializable]
    public sealed class Class1
    {
        static AppDomain dom;

        [CommandMethod("bush", "dload", CommandFlags.Session)]
        public static void Load()
        {
            if (dom != null)
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage("The domain already exists.\n");
                return;
            }
            dom = AppDomain.CreateDomain("TestDomain");
            try
            {                
                dom.DoCallBack(new CrossAppDomainDelegate(LoadDll));//I get exception in this row!
            }
            catch (System.Exception ex)
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("Excetion message: \"{0}\"\n", ex.Message));
                return;
            }
            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage("Successful loading.\n");
        }

        [CommandMethod("bush", "dunload", CommandFlags.Session)]
        public static void Unload()
        {
            if (dom == null)
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage("The domain isn't created yet! At first launch a command dload.\n");
                return;
            }            
            try
            {
                AppDomain.Unload(dom);
                dom = null;                
            }
            catch (System.Exception ex)
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("Excetion message: \"{0}\"\n", ex.Message));
                return;
            }
            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage("Successful unloading.\n");
        }

        static void LoadDll() {
            //Empty method body!!!
            //Assembly asm = Assembly.LoadFrom(@"D:\MD\Visual Studio 2010\CmdSample\CmdSample\bin\Debug\CmdSample.dll");
        }        
    }
}
AutoCAD command string:
Quote
Command: netload

Command: dload
Excetion message: "Could not load file or assembly 'DomainUseSample,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its
dependencies. It is not possible to find the specified file."
What to hell a file???

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Load/Unload .net-dll in special AppDomain
« Reply #2 on: June 20, 2011, 06:24:04 AM »
From ADN:
Quote
Issue
Like NETLOAD, is there a NETUNLOAD?

Solution
No, there isn't. DotNET does not have the concept of unloading an assembly. We would need to host your assembly in a separate appdomain and shut down the appdomain to unload your assembly. This was deemed too much hasle for little gain for now. (We may do this to provide better security and versioning guarantees in the future)
 
Note that you can do this if you want yourself. You would need to create a bootstrapper assembly that defines 2 commands: MyNetload and MyNetUnload. MyNetload would need to create a new AppDomain and load the user specified assembly into this appdomain. MyNetUnload could then unload the AppDomain effectively unloading the user's assembly.
But it and so any.Net-programmer knows...  :-(
I can't find code sample...
« Last Edit: June 20, 2011, 06:38:20 AM by Andrey »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Load/Unload .net-dll in special AppDomain
« Reply #3 on: June 21, 2011, 01:01:11 PM »
The subject can be closed - the question is solved: it was necessary to write the handler of event AssemblyResolve.
« Last Edit: June 21, 2011, 01:09:56 PM by Andrey »

hermanm

  • Guest
Re: Load/Unload .net-dll in special AppDomain
« Reply #4 on: June 22, 2011, 09:27:04 AM »
Care to post the code?

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Load/Unload .net-dll in special AppDomain
« Reply #5 on: June 22, 2011, 11:46:38 PM »
Care to post the code?
If it is interesting - my example of a code here.

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: Load/Unload .net-dll in special AppDomain
« Reply #6 on: June 22, 2011, 11:59:33 PM »
However last night I have received the answer to the question which set in ADN:
Quote
Hi Andrey,
 
Below is an attempt I did a while ago to load an AutoCAD .Net assembly in a separate custom AppDomain.
 
I was able to get it to load and call a method of the loaded assembly across domains, unfortunately when unloading the custom AppDomain and closing AutoCAD it will crash.
 
I consulted the development team who confirmed that some work was to do in AutoCAD core .Net functionalities in order to be able to achieve this workflow. At the moment there are some global variables (cross domains) which will prevent that code to work properly. Although problems seem to arise only during close up of AutoCAD.
 
You may be interested by that piece of code so far. I also gathered links you may find helpful. You will be able to find much more information on Microsoft side concerning AppDomains.
 
I Hope it helps.
 
http://bytes.com/topic/c-sharp/answers/251643-good-old-assembly-unload-doesnt-exist
http://bytes.com/topic/c-sharp/answers/474227-load-unload-assembly-system-appdomain

Code: [Select]
namespace DotnetLoader
{
    public partial class AppDomainLoader
    {
        AppDomain _Domain;
 
        AssemblyProxy _AsmProxy;
 
        private void LoadAsmInAppDomain(string asmPath)
        {
            AppDomainSetup appSetup = new AppDomainSetup();
 
            _Domain = AppDomain.CreateDomain("MyAppDomain", null, appSetup);
 
            _Domain.DoCallBack(LoaderCallback);
 
            _Domain.AssemblyResolve += new ResolveEventHandler(AssemblyResolve);
 
            AssemblyName asmName = AssemblyName.GetAssemblyName(asmPath);
 
            _AsmProxy = AssemblyProxy.CreateProxy(_Domain, asmPath);
        }
 
        private void LoaderCallback()
        {
            //_Asssembly = Assembly.LoadFrom(_AsmPath);
        }
 
        Assembly AssemblyResolve(object sender, ResolveEventArgs args)
        {
            //This handler is called only when the common language runtime tries to bind to the assembly and fails.
 
            //Retrieve the list of referenced assemblies in an array of AssemblyName.
            string strTempAssmbPath = "";
 
            Assembly objExecutingAssemblies = Assembly.GetExecutingAssembly();
 
            AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
 
            //Loop through the array of referenced assembly names.
            foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
            {
                //Check for the assembly names that have raised the "AssemblyResolve" event.
                if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
                {
                    //Build the path of the assembly from where it has to be loaded.                      
                    strTempAssmbPath = "C:\\Myassemblies\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
                    break;
                }
 
            }
 
            //Load the assembly from the specified path.                          
            Assembly MyAssembly = Assembly.LoadFrom(strTempAssmbPath);
 
            //Return the loaded assembly.
            return MyAssembly;    
        }
 
        void UnloadAppDomain()
        {
            _AsmProxy = null;
            AppDomain.Unload(_Domain);
        }
    }
 
    class AssemblyProxy : MarshalByRefObject
    {
        private AppDomain _Domain;
 
        private Assembly _Assembly;
 
        private List<TypeProxy> _Types;
 
        public static AssemblyProxy CreateProxy(AppDomain domain, string asmPath)
        {
            Assembly asm = Assembly.GetExecutingAssembly();
 
            string TypeName = typeof(AssemblyProxy).FullName;
 
            AssemblyProxy proxy = domain.CreateInstanceAndUnwrap(asm.FullName, TypeName) as AssemblyProxy;
 
            proxy.LoadAssembly(asmPath);
 
            proxy.LoadAssemblyInfos();
 
            return proxy;
        }
 
        private bool LoadAssembly(string asmPath)
        {
            System.Security.Policy.Evidence evidence = new System.Security.Policy.Evidence();
 
            _Assembly = Assembly.LoadFile(asmPath, evidence);
 
            return true;
        }
 
        private void LoadAssemblyInfos()
        {
            Type[] Types = _Assembly.GetExportedTypes();
 
            _Types = new List<TypeProxy>();
 
            foreach (Type Type in Types)
            {
                _Types.Add(new TypeProxy(Type));
            }
        }
 
        public TypeProxy[] GetExportedTypes()
        {
            return _Types.ToArray();
        }
 
        public void InvokeMethod(TypeProxy Type, MethodInfoProxy MethodInfo)
        {
            object instance = _Assembly.CreateInstance(Type.FullName);
 
            MethodInfo.Invoke(instance);
        }
    }
 
    class TypeProxy : MarshalByRefObject
    {
        private Type _Type;
 
        private List<MethodInfoProxy> _Methods;
 
        public TypeProxy(Type Type)
        {
            _Type = Type;
 
            _Methods = new List<MethodInfoProxy>();
 
            foreach (MethodInfo method in _Type.GetMethods())
            {
                _Methods.Add(new MethodInfoProxy(method));
            }
        }
 
        public string Name
        {
            get
            {
                return _Type.Name;
            }
        }
 
        public string FullName
        {
            get
            {
                return _Type.FullName;
            }
        }
 
        public MethodInfoProxy[] GetMethods()
        {
            return _Methods.ToArray();
        }
    }
 
    class MethodInfoProxy : MarshalByRefObject
    {
        private MethodInfo _MethodInfo;
 
        public MethodInfoProxy(MethodInfo MethodInfo)
        {
            _MethodInfo = MethodInfo;
        }
 
        public string Name
        {
            get
            {
                return _MethodInfo.Name;
            }
        }
 
        public void Invoke(object instance)
        {
            object ret = _MethodInfo.Invoke(instance, BindingFlags.InvokeMethod, null, null, null);
        }
 
        public void GetAttributes()
        {
            object[] attributes = _MethodInfo.GetCustomAttributes(true);
        }
    }
}


hermanm

  • Guest
Re: Load/Unload .net-dll in special AppDomain
« Reply #7 on: June 23, 2011, 10:26:52 PM »
Thanks, Andrey.