none
Why does AppDomain.CreateInstanceAndUnwrap(..) work and AppDomain.CreateInstanceFrom(...).UnWrap doesn't?

    Question

  • Why does AppDomain.CreateInstanceAndUnwrap(..) work and AppDomain.CreateInstanceFrom(...).UnWrap doesn't?


    I'm trying to load an assembly in a AppDomain. The AppDomain.CreateInstanceAndUnwrap works but only when the assembly is in the GAC. I do not want to use the GAC so I've tried to use the AppDomain.CreateInstanceFrom and then use the ObjectHandle.Unwrap() to ge the Object but this fails when I try to cast it to my Object with:

    Unable to cast transparent proxy to type 'Configuration.CompileProxy.Compiler'.

    Here's the code:

                    AppDomain ad = AppDomain.CreateDomain("Temp Domain"); // create a new
                    try
                      {
                          //Configuration.CompileProxy.Compiler galaxyCompiler = (Configuration.CompileProxy.Compiler)ad.CreateInstanceAndUnwrap("Configuration.CompileProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f77dad97b7dc0c2", "Configuration.CompileProxy.Compiler");//GAC version

                          //Configuration.CompileProxy.Compiler galaxyCompiler = ((Configuration.CompileProxy.Compiler)ad.CreateInstanceFrom("C:\\GalaxyCompileProxy\\bin\\Debug\\Configuration.CompileProxy.dll", "Avaeon.Configuration.CompileProxy.Compiler").Unwrap());//Direct version

    Wednesday, August 23, 2006 3:14 PM

Answers

  • Solution found.
    The solution is to subscribe to the AssemblyResolve Event for the first AppDomain (this is if you use the CreateInstanceAndUnWrap() which also requires you to set the AppBase of your new AppDomain to the location of your assemblies).

    (I did notice an infinite loop when subscribing to this event before, maybe it was because I was using CreateInstanceFromAndUnWrap()).


    [CODE] private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            {
                string projectDir = Path.GetDirectoryName(Configuration.GalaxyBuildTask.GalaxyBuildTask._presentProjectLocation);
                string shortAssemblyName = args.Name.Substring(0, args.Name.IndexOf(','));
                string fileName = Path.Combine(projectDir, shortAssemblyName + ".dll");
                if (File.Exists(fileName))
                {
                        Assembly result = Assembly.LoadFrom(fileName);
                        return result;
                }
                else
                  return Assembly.GetExecutingAssembly().FullName == args.Name ? Assembly.GetExecutingAssembly() : null;

            }
    [/CODE]

    the Configuration.GalaxyBuildTask.GalaxyBuildTask._presentProjectLocation is set earlier using
    _presentProjectLocation = BuildEngine.ProjectFileOfTaskNode;
    this sets the location to be that of the current VS project, because this is launched from a build task it is required to locate the current project.

    Tuesday, August 29, 2006 3:12 PM

All replies

  • I don't understand this, if I drop all the dependant assemblies into the VS installation dir , C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies , then all works fine using

    AppDomain ad = AppDomain.CreateDomain("Compiling");
    Configuration.CompileProxy.Compiler compiler = (Configuration.CompileProxy.Compiler)ad.CreateInstanceAndUnwrap("Configuration.CompileProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f77dad97b7dc0c2", "Configuration.CompileProxy.Compiler");

    And the same applies if I simple add the assembly, Configuration.CompileProxy.dll, to the GAC on it's own.


    I've tried appending the paths of the AppDomain too using
    ad.AppendPrivatePath("System.Environment.CurrentDirectory);

    and dropping all the depentant assemlies into that current directory but that doesn't work either!!!!


    Any one have any idea what the problem is. I've looked at various articals and all use the example above or something similar. :confused:

    Thursday, August 24, 2006 2:59 PM
  • Probably because you're using a path when loading the assembly. http://www.gotdotnet.com/team/clr/LoadFromIsolation.aspx You can use fuslogvw.exe to see exactly what happens.
    Monday, August 28, 2006 11:47 AM
  • Hi Lucian, you're the first to reply and I am grateful,  i thought I was alone here for the past week.
    I've come across the article before, so if I use CreateInstanceFromAndUnWrap() it behaves like LoadFrom() and I cannot perform a cast on the result, is that correct? so how else can I invoke the method in an instance of my remote object is the assembly is not in the GAC?


    I've tried setting the AppBase to the location of my assemblies, but no good, still get the type cast error
     
                AppDomainSetup appSetup = new AppDomainSetup();
                appSetup.ApplicationBase =  "C:\\safewaygalaxy\\Metadata";
                appSetup.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
                AppDomain ad = AppDomain.CreateDomain("Compiling Gravity", null, appSetup);
       Avaeon.Configuration.CompileProxy.Compiler galaxyCompiler = (Avaeon.Configuration.CompileProxy.Compiler)ad.CreateInstanceAndUnwrap("Avaeon.Configuration.CompileProxy", "Avaeon.Configuration.CompileProxy.Compiler");
                
    I've been playing with the fuslogvw this morning, here is the log, there are 5 log entries in total, here are the last 2, the others appear as Visual Studio type logs

    Again the error is
    Error: Unable to cast transparent proxy to type 'Avaeon.Configuration.CompileProxy.Compiler'.


    LOG # 1
    *** Assembly Binder Log Entry  (8/28/2006 @ 3:20:44 PM) ***

    The operation was successful.
    Bind result: hr = 0x0. The operation completed successfully.

    Assembly manager loaded from: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
    Running under executable C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe
    --- A detailed error log follows.

    === Pre-bind state information ===
    LOG: User = AVAEON\nfallon
    LOG: DisplayName = Avaeon.Configuration.CompileProxy
    (Partial)
    LOG: Appbase = file:///C:/safewaygalaxy/Metadata
    LOG: Initial PrivatePath = C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\
    LOG: Dynamic Base = NULL
    LOG: Cache Base = NULL
    LOG: AppName = NULL
    Calling assembly : (Unknown).
    ===
    LOG: This bind starts in default load context.
    LOG: Using application configuration file: C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe.Config
    LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
    LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
    WRN: Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy.DLL, because the location falls outside of the appbase.
    WRN: Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy/Avaeon.Configuration.CompileProxy.DLL, because the location falls outside of the appbase.
    WRN: Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy.EXE, because the location falls outside of the appbase.
    WRN: Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy/Avaeon.Configuration.CompileProxy.EXE, because the location falls outside of the appbase.
    LOG: Attempting download of new URL file:///C:/safewaygalaxy/Metadata/Avaeon.Configuration.CompileProxy.DLL.
    LOG: Assembly download was successful. Attempting setup of file: C:\safewaygalaxy\Metadata\Avaeon.Configuration.CompileProxy.dll
    LOG: Entering run-from-source setup phase.
    LOG: Assembly Name is: Avaeon.Configuration.CompileProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f77dad97b7dc0c2
    LOG: A partially-specified assembly bind succeeded from the application directory. Need to re-apply policy.
    LOG: Using application configuration file: C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe.Config
    LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
    LOG: Post-policy reference: Avaeon.Configuration.CompileProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f77dad97b7dc0c2
    LOG: GAC Lookup was unsuccessful.
    WRN: Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy.DLL, because the location falls outside of the appbase.
    WRN: Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy/Avaeon.Configuration.CompileProxy.DLL, because the location falls outside of the appbase.
    WRN: Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy.EXE, because the location falls outside of the appbase.
    WRN: Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy/Avaeon.Configuration.CompileProxy.EXE, because the location falls outside of the appbase.
    LOG: Binding succeeds. Returns assembly from C:\safewaygalaxy\Metadata\Avaeon.Configuration.CompileProxy.dll.



    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    LOG # 2


    *** Assembly Binder Log Entry (8/28/2006 @ 3:20:44 PM) ***

    The operation failed.
    Bind result: hr = 0x80070002. The system cannot find the file specified.

    Assembly manager loaded from: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
    Running under executable C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe
    --- A detailed error log follows.

    === Pre-bind state information ===
    LOG: User = AVAEON\nfallon
    LOG: DisplayName = Avaeon.Configuration.CompileProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f77dad97b7dc0c2
    (Fully-specified)
    LOG: Appbase = file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/
    LOG: Initial PrivatePath = NULL
    LOG: Dynamic Base = NULL
    LOG: Cache Base = NULL
    LOG: AppName = NULL
    Calling assembly : (Unknown).
    ===
    LOG: This bind starts in default load context.
    LOG: Using application configuration file: C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe.Config
    LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
    LOG: The same bind was seen before, and was failed with hr = 0x80070002.
    ERR: Unrecoverable error occurred during pre-download check (hr = 0x80070002).

    LOG: Assembly is loaded in default load context.

    Monday, August 28, 2006 2:32 PM
  • Your custom AppDomain can find the assembly and your setting of PrivateBinPath is not useful and is ignored: LOG: Appbase = file:///C:/safewaygalaxy/Metadata Not probing location file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/Avaeon.Configuration.CompileProxy.DLL, because the location falls outside of the appbase.

    LOG: Binding succeeds. Returns assembly from C:\safewaygalaxy\Metadata\Avaeon.Configuration.CompileProxy.dll

    The VS AppDomain cannot find the assembly: LOG: Appbase = file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/, LOG: The same bind was seen before, and was failed with hr = 0x80070002.

    So you either use the same Appbase for both AppDomains or you put the assembly in each Appbase directory and let the CLR find them.

    Monday, August 28, 2006 2:46 PM
  • Thanks again Lucian,
    I haven't changed the AppBase a second time, it is the same AppDomain!

    Adding the assemblies to the file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/ is not an option.
    Using the
    Appbase = file:///C:/safewaygalaxy/Metadata is exactly what I want and as we've seen works in the first log for assembly Avaeon.Configuration.CompileProxy
    LOG: DisplayName = Avaeon.Configuration.CompileProxy
    But it creates a second log file for what looks like a version of the same assembly
    LOG: DisplayName = Avaeon.Configuration.CompileProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1f77dad97b7dc0c2


    I don't understand what's happening here, why there are 2 log entries, this is the one call to cast the type
    Avaeon.Configuration.CompileProxy.Compiler galaxyCompiler = (Avaeon.Configuration.CompileProxy.Compiler)ad.CreateInstanceAndUnwrap("Avaeon.Configuration.CompileProxy", "Avaeon.Configuration.CompileProxy.Compiler");

    Monday, August 28, 2006 2:58 PM
  • Maybe I'm mising your point but you do have 2 appdomains, as you can see from the log, each with a different appbase. Each appdomain needs to find the assembly it uses. You can also print somehow the appdomain id to verify this. Assemblies with strong names should be in the GAC.
    Monday, August 28, 2006 3:04 PM
  • This all happening in the same AppDomain, I see that the AppBase changes for the second log/assembly, I don't know why!!! The code I posted is complete

    I don't know why there are 2 logs it's the one call to create the instance.

    The assemblies are signed, for other reasons. I've removed the signing for now but now noticed that the log still reports the assembly with the strong name!!! I've removed all references to the dll on my harddrive but still it persists, any idea whats picking up this strong name?

    GAC is not an option either.

    Monday, August 28, 2006 3:38 PM
  • If I break up the instance creation into 2 seperate calls I can see where the 2 logs are coming from
    System.Runtime.Remoting.ObjectHandle objh = ad.CreateInstance("Avaeon.Configuration.CompileProxy", "Avaeon.Configuration.CompileProxy.Compiler");

    Avaeon.Configuration.CompileProxy.Compiler galaxyCompiler = (Avaeon.Configuration.CompileProxy.Compiler)objh.Unwrap();

    The first is successfull and uses the correct weak named version of the assembly.
    However it is the call to UnWrap() that causes the problem, it uses a different AppBase and also tries to unwrap to the strong named version (which no longer exists) !!!!
    Actually it's the fullname or something that it UnWrap returns
    "DisplayName = Avaeon.Configuration.CompileProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    notice the PublicToken is null.






    Monday, August 28, 2006 4:13 PM
  • So Suzanne touched on something in one of her blogs http://blogs.msdn.com/suzcook/archi...5/29/57143.aspx, 

    "Thanks, Yiru! For your question: say your AppDomain's ApplicationBase
    is C:\foo, and you've done a LoadFrom(@"C:\bar\A.dll"). So, assembly A
    is in the LoadFrom context. If you serialize A, and then deserialize it
    in that appdomain, deserialization will call Load("A, [...]"). (Note
    that path info is not included in that call.) If A is not available in
    the Load context, you'll get an exception, even though A is already
    loaded in the appdomain. You would have to subscribe to the
    AssemblyResolve event to resolve it. But, worse, if another copy of A
    is available in the GAC or beneath the ApplicationBase, then Load()
    will load that instead of the one in the C:\bar dir. Then, both A.dll's
    will be loaded in the same appdomain, and their types will not be
    castable to each other."



    This looks like my problem. I'm loading assemblies from a specific
    location other than the one where the actual application is starting
    from. I can try to instantiate the assemblies by either using changing
    the AppBase and caling CreateInstanceAndUnWrap() or by calling
    CreateInstanceFromAndUnWrap(), both result in the same problem, the
    UnWrap part of the call results in cast failure. This is what Suzanne
    is referring to I think, the assembly first loaded is not the same as
    the assembly being UnWrapped.



    The reason why my case is unique I think is because of the way in which
    the application is being launched, it's a BuildTask, the BaseDirectory
    of the app is C:\Program Files\Microsoft Visual Studio 8\Common7\IDE, I
    change the ApplicationBase to my own location but it appears to be
    ignored once the UnWrap comes into play, I think the UnWrap only uses
    the Load Context, which is the GAC or ApplicationBase, I do not know
    why the ApplicationBase reverts to the initial ApplicationBase of the
    original AppDomain, anyone??

    I've verified that my code is good as these assemblies load without
    issue if I run this code from a test console application and not a
    BuildTask.





    Here's the log from the Fusion Logger (This is at the UnWrap):


    *** Assembly Binder Log Entry (8/29/2006 @ 12:06:42 PM) ***

    The operation failed.
    Bind result: hr = 0x80070002. The system cannot find the file specified.

    Assembly manager loaded from: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
    Running under executable C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe
    --- A detailed error log follows.

    === Pre-bind state information ===
    LOG: User = AVAEON\nfallon
    LOG: DisplayName = Avaeon.Configuration.CompileProxy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
    (Fully-specified)
    LOG: Appbase = file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE/
    LOG: Initial PrivatePath = NULL
    LOG: Dynamic Base = NULL
    LOG: Cache Base = NULL
    LOG: AppName = NULL
    Calling assembly : (Unknown).
    ===
    LOG: This bind starts in default load context.
    LOG: Using application configuration file: C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe.Config
    LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
    LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
    LOG: The same bind was seen before, and was failed with hr = 0x80070002.
    ERR: Unrecoverable error occurred during pre-download check (hr = 0x80070002).
    Tuesday, August 29, 2006 11:37 AM
  • Solution found.
    The solution is to subscribe to the AssemblyResolve Event for the first AppDomain (this is if you use the CreateInstanceAndUnWrap() which also requires you to set the AppBase of your new AppDomain to the location of your assemblies).

    (I did notice an infinite loop when subscribing to this event before, maybe it was because I was using CreateInstanceFromAndUnWrap()).


    [CODE] private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            {
                string projectDir = Path.GetDirectoryName(Configuration.GalaxyBuildTask.GalaxyBuildTask._presentProjectLocation);
                string shortAssemblyName = args.Name.Substring(0, args.Name.IndexOf(','));
                string fileName = Path.Combine(projectDir, shortAssemblyName + ".dll");
                if (File.Exists(fileName))
                {
                        Assembly result = Assembly.LoadFrom(fileName);
                        return result;
                }
                else
                  return Assembly.GetExecutingAssembly().FullName == args.Name ? Assembly.GetExecutingAssembly() : null;

            }
    [/CODE]

    the Configuration.GalaxyBuildTask.GalaxyBuildTask._presentProjectLocation is set earlier using
    _presentProjectLocation = BuildEngine.ProjectFileOfTaskNode;
    this sets the location to be that of the current VS project, because this is launched from a build task it is required to locate the current project.

    Tuesday, August 29, 2006 3:12 PM