locked
AppDomain not working for Extension of Visual Studio RRS feed

  • Question

  • I have developed an extension for Visual Studio. And in my extension code, I am trying to load a dll into my own new AppDomain. Below is the code which I am trying.

     AppDomainSetup setupInfo = new AppDomainSetup();
                info.ApplicationBase = @"C:\Users\xxx\AppData\Local\Microsoft\VisualStudio\15.0_60bdd44eExp\Extensions\National Instruments\Project1\1.0\";
                appDomain = AppDomain.CreateDomain("mydomain", null, setupInfo);
                var someclassObj1 = (someclass)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(someclass).FullName);
                var returnValue = someclassObj.getsomething();

    I am getting "Unable to cast transparent proxy to type 'Project1.Model.someclass'.

    what am I missing here.

    • Moved by CoolDadTx Wednesday, May 20, 2020 1:41 PM VS related
    Wednesday, May 20, 2020 9:57 AM

All replies

  • Firstly be aware that much of VS won't work outside the AppDomain your extension is running in, unless they've made progress there. Even a lot of the core services won't work based upon my time trying to get extensions working in more complex situations.

    As for the error itself it may just be a normal app domain issue. In order to load items across app domains the type must derive from MarshalByRefObject. Additionally all of its members need to be marshalable as well. Domains are memory boundaries so in order to work every type that must be usable in different domains must have a runtime proxy generated. That proxy handles marshaling the call from the calling domain to the owning domain. All this requires implicit support from the type via the base type given earlier. If you don't derive from that type you cannot proxy calls.

    Personally I think you should not bother with a separate appdomain unless you really need the isolation that it provides. For other purposes consider using one of the load context instead (e.g. reflection).


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, May 20, 2020 1:41 PM
  • Thanks @CoolDadTx for the response.

    In my case, I wanted to reload the DLL as it will be changed by user. So if I load the DLL in my current Domain, it doesn't have an option to unload. That's why I went with new AppDomain and I am loading my DLL in to the new AppDomain. If I want to reload the DLL, I will unload my AppDomain and Create a new Domain again.

    The someclass is derived from "MarshalByRefObject" class. I have inherited it. Below is the code.

        public class someclass : 
            MarshalByRefObject
        {
    
            private static Func<object> FuncConstr { get; }
            private object Instance { get; }
            private static Func<object, double> FuncExecute { get; }
            public static Assembly assembly { get; }
    
           static someclass()
            {
    
                var assemblyPath = @"C:\xxx\yyy\Demo.dll";
                
                assembly = Assembly.LoadFrom(assemblyPath);
                Type My_Type = assembly.GetType("NameSpace1." + "Class1");
                FuncConstr = () => Activator.CreateInstance(My_Type);
                MessageBox.Show("static class");
            }
            public someclass() =>
                Instance = FuncConstr();
        
            public string getsomething() =>
                FuncExecute(Instance).ToString();
    
        }

    Even though I have derived MarshalByRefObject class, it throws the error. Please let me know if I am missing any here.

    Thanks in Advance.

    Thursday, May 21, 2020 4:06 AM
  • I have developed a Visual Studio Extension window. In that Extension Window, I am trying to load an external DLL (class library) and will be accessing the Class, its Methods and properties of that class inside the external DLL. Now I am allowing the user to interact with my Extension Window by choosing the methods from that DLL and allowing them to execute those methods.

    Now comes the tricky part, I will be getting a newer version of that external DLL. So I need to unload my older version of external DLL and Load the newer version of external DLL.

    If I load the external DLL normally using Assembly class, then it will load the DLL into the Current Domain and can't be unloaded. So I am trying to load the external DLL into a new my own AppDomain so that I can unload the AppDomain which unloads the DLL automatically.

    Below is the code which I am calling it from my Extension Window code,

    AppDomainSetup appSetup = new AppDomainSetup(); 
    
    appSetup.ApplicationBase = @"C:\Users\xxx\AppData\Local\Microsoft\VisualStudio\15.0_60bdd44eExp\Extensions\National Instruments\Project1\1.0"; 
    appDomain = AppDomain.CreateDomain("mydomain", null, appSetup); 
    var someclassObj = (someclass)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(someclass).FullName);

    In the above code, Line#2 is necessary because it is an Extension project and runs on Visual Studio instance. If I don't specify AppDomainSetup with the Path to the Extension folder where it is running, it throws the error "Not able to load the DLL as one of the dependencies is not found". So after configuring the Extension folder path to AppDomainSetup, it is able to get into someclass constructor and loads the DLL into a new Domain.

    Now the problem that I am facing is, when it tries to create the object for the someclass, it throws the error "Unable to cast transparent proxy to type 'Namespace1.someclass". in Line#4 (CreateInstanceandUnWrap). I think it gives out the object of type Marshal instead of someclass. Please help me on this error.

    Below is my someclass code for reference.

    public class someclass : MarshalByRefObject { private static Func<object> FuncConstr { get; } private object Instance { get; } private static Func<object, double> FuncExecute { get; } public static Assembly assembly { get; } static someclass() { var assemblyPath = @"C:\xxx\yyy\Demo.dll"; assembly = Assembly.LoadFrom(assemblyPath); Type My_Type = assembly.GetType("NameSpace1." + "Class1");

    FuncConstr = () => Activator.CreateInstance(My_Type);

    MessageBox.Show("static class"); } public someclass() => Instance = FuncConstr(); public string getsomething() => FuncExecute(Instance).ToString(); }

    Any help on this would be really helpful.



    Thursday, May 21, 2020 8:50 AM
  • This is probably not going to work out for you, especially as an extension of VS. In order to unload the domain without error all references to all objects in that domain must go away. Even if you're diligently cleaning up resources there is no guarantee. At the point anybody references the proxy that was in the (now gone) appdomain then it'll crash. This is such a difficult problem to solve that even VS doesn't actually do that AFAIK. You can disable extensions but they are still loaded until VS restarts. It simply isn't worth the hassle.

    It is also important to note that the CLR tracks objects by their fully qualified type name. It sounds like your specific case involves you changing the assembly. If you do that then the CLR is going to see it as a new type most likely. This means your existing proxies won't work.

    Looking at your SomeClass code, it isn't going to work. The way remoting works is that the type is loaded in both domains. Static members are created in both domains. So let's play through what is happening here.

    1. You create your separate appdomain.

    2. You call appDomain.CreateInstanceAndUnwrap.
        a. The SomeClass is referenced for the first time so the static ctor runs.    
        b. The static ctor loads an assembly.
        c. The new instance of SomeClass is created.
        d. The instance ctor is run which creates an instance of the type defined earlier by the static ctor.
    3. The proxy instance is returned but this requires SomeClass so the type is loaded in the current (main) appdomain.
        a. The SomeClass static ctor is run (actually it probably runs before the calling method even executes).
        b. The static ctor loads the assembly you only wanted loaded in the other appdomain.
        c. The proxy is created to wrap the remote instance.

    At this point you've defeated the purpose of using multiple appdomains. The assembly you only wanted to be loaded over in the other appdomain is loaded into both because the SomeClass will be referenced in both domains. Since the SomeClass has a static ctor that loads the "user" assembly that assembly gets loaded in both appdomains. Now if you unload the other app domain the "user" assembly is still loaded in the current domain. You cannot "swap" types at runtime even if they have the same names. The CLR doesn't support it.

    At a minimum you need to remove the static ctor and only load the assembly when it is needed. That means only when the main domain calls a method that the proxy will forward to the remote domain to then run. Anything defined static will be loaded into both domains.


    Michael Taylor http://www.michaeltaylorp3.net

    Thursday, May 21, 2020 2:06 PM
  • Please do not post the same question multiple times. This just adds confusion. Adding link to other question so everyone can get the context.

    Michael Taylor http://www.michaeltaylorp3.net

    Thursday, May 21, 2020 2:22 PM
  • Thanks @CoolDadTx for the detailed information.

    The loading of assembly in static ctor of someclass loads my assembly only in new appdomain (NOT in both DefaultDomain and MyDomain). The reason is, SomeClass is being called from my new AppDomain, so even though it is static constructor, it will be loading into new AppDomain. Static is being used mainly to avoid loading it multiple times. I have verified the loading of dll into AppDomains in Modules Window. (I couldn't attach the image here).

    As per your suggestion, I have tried removing the loading of assembly in Static ctor and loaded from normal ctor, but no change in the results. That is, DLL is getting loaded into new AppDomain alone, but still getting the same cast error.

    I appreciate your help on this.

    Friday, May 22, 2020 4:24 AM
  • Ok I'm trying to replicate your code and there are some inconsistencies. If this is indeed directly copied from your existing code then it may indicate the issue. 

    1. You new up an instance of AppDomainSetup into `setupInfo`. But on the next line you're setting the application base for `info`. Where is `info` defined?

    2. When you create an instance in the remote domain for someclass you're using the current executing assembly's name, not the name of the assembly that should only be loaded in the remote app domain. Now if this code is running in the secondary domain already then why are you creating a third appdomain from the second appdomain? Irrelevant someclass isn't defined in the current assembly unless someclass AND the method you posted are in the same assembly. If they are then the assembly (and static ctor) are going to execute in this assembly which defeats the purpose you were trying to accomplish. I fixed this by using the assembly name from the type of someclass.

    3. You are putting the result of the create into `someclassObj1`. But the next line is calling `someclassObj`. Where did that come from? Did you happen to copy paste this code into the same method twice and then forget to rename some variables? Is `someclassObj` actually still a proxy for the previous domain?

    4. Even after fixing these issues calling `getsomething` throws an exception because FuncExecute is never set.

    Once I fixed these errors the code ran correctly but I still have concerns. I'd start with the fact that LoadFrom is not a good context to use. Refer to the docs on why. You might consider using LoadFile instead. It isn't much better.

    For reference here is my code.

    //Main console assembly Program class
    static void Main(string[] args)
    {
        Logger.Log("Main called");
    
        var setupInfo = new AppDomainSetup();
    
        //NOTE: This is different in your app we're you're setting info's base, not setupInfo...
        setupInfo.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
    
        var appDomain = AppDomain.CreateDomain("mydomain", null, setupInfo);
    
        //NOTE: The current assembly's name??
        //var asmName = Assembly.GetExecutingAssembly().FullName;
        var asmName = typeof(someclass).Assembly.FullName;
    
        var someclassObj1 = (someclass)appDomain.CreateInstanceAndUnwrap(asmName, typeof(someclass).FullName);
    
        //NOTE: This is different - you are using someclassObj
        var returnValue = someclassObj1.getsomething();
    
        //Unload the domain
        Logger.Log("Unloading appdomain");
        AppDomain.Unload(appDomain);
        appDomain = null;
    
        //Try again
    
        var setupInfo2 = new AppDomainSetup();
    
        //NOTE: This is different in your app we're you're setting info's base, not setupInfo...
        setupInfo2.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
    
        var appDomain2 = AppDomain.CreateDomain("mydomain", null, setupInfo2);
    
        //NOTE: The current assembly's name??
        //var asmName2 = Assembly.GetExecutingAssembly().FullName;
        var asmName2 = typeof(someclass).Assembly.FullName;
    
        var someclassObj2 = (someclass)appDomain2.CreateInstanceAndUnwrap(asmName2, typeof(someclass).FullName);
    
        //NOTE: This is different - you are using someclassObj
        var returnValue2 = someclassObj2.getsomething();
    
        //Unload the domain
        Logger.Log("Unloading appdomain");
        AppDomain.Unload(appDomain2);
        appDomain2 = null;
    }        
    
    //SharedLib assembly used by main assembly
    
    public class someclass :
        MarshalByRefObject
    {
    
        private static Func<object> FuncConstr { get; }
        private object Instance { get; }                      
        private static Func<object, string> FuncExecute { get; }
        public static Assembly assembly { get; }
    
        static someclass()
        {
            Logger.Log("someclass::cctor called");
    
            var assemblyPath = System.IO.Path.GetFullPath(@"demo.dll");
    
            Logger.Log($"Assembly '{assemblyPath}' loading");
            assembly = Assembly.LoadFrom(assemblyPath);
            Type My_Type = assembly.GetType("NameSpace1." + "Class1");
    
            Logger.Log($"Creating instance of type '{My_Type}'");
            FuncConstr = () => Activator.CreateInstance(My_Type);
            Logger.Log($"Assembly '{assemblyPath}' loaded");
    
            //NOTE: Working around fact that this is never set, changed return type to string
            FuncExecute = obj => obj.ToString();
        }
    
        public someclass()
        {
            Logger.Log("someclass::ctor called");
            Instance = FuncConstr();
        }
    
        public string getsomething()
        {
            Logger.Log("someclass::getsomething called");
            return FuncExecute(Instance).ToString();
        }
    }
    
    //Demo test assembly, not referenced by anyone but copied into main assemblies output at build
    namespace NameSpace1
    {
        public class Class1
        {
        }
    }
    
    


    Michael Taylor http://www.michaeltaylorp3.net

    Friday, May 22, 2020 4:01 PM
  • @CoolDadTx - Yes. Your assumptions are correct withrespect to variables. Since I copied the code from my prototype, it was having mismatch.

    #2 -> I did try both Assembly of someclass and Executing Assembly (refer below). I was getting the same cast error.

        //var asmName2 = Assembly.GetExecutingAssembly().FullName;
        var asmName2 = typeof(someclass).Assembly.FullName;

    As you tried from Console App, I was also able to run this code without any issues in Console, WinForms, Wpf Apps. I am facing this "Unable to Cast" error only when I try it from Extension Type of Project.


    Tuesday, May 26, 2020 4:10 AM
  • Where the code executes could be important. It might be necessary to move this question to the VS Integrate forum as it appears to be related to VS extensions and not the code itself. This could be caused by any number of things including whether it is being called on package load, as response to a command, etc. It would also depend upon whether the extension was loaded into its own app domain or shared with others. In the past you had control over this but I don't know that the newer project templates allow it.

    Please post the full relevant code related to the actual code you're executing and the extension code it is running in (package, command handler, etc).


    Michael Taylor http://www.michaeltaylorp3.net

    Tuesday, May 26, 2020 1:31 PM