Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Unable to cast transparent proxy to type <type>


:P
On this page:

This is not the first time I've run into this wonderful error while creating new AppDomains in .NET and then trying to load types and access them across App Domains.

In almost all cases the problem I've run into with this error the problem comes from the two AppDomains involved loading different copies of the same type. Unless the types match exactly and come exactly from the same assembly the typecast will fail. The most common scenario is that the types are loaded from different assemblies - as unlikely as that sounds.

An Example of Failure

To give some context, I'm working on some old code in Html Help Builder that creates a new AppDomain in order to parse assembly information for documentation purposes. I create a new AppDomain in order to load up an assembly process it and then immediately unload it along with the AppDomain. The AppDomain allows for unloading that otherwise wouldn't be possible as well as isolating my code from the assembly that's being loaded.

The process to accomplish this is fairly established and I use it for lots of applications that use add-in like functionality - basically anywhere where code needs to be isolated and have the ability to be unloaded. My pattern for this is:

  • Create a new AppDomain
  • Load a Factory Class into the AppDomain
  • Use the Factory Class to load additional types from the remote domain

Here's the relevant code from my TypeParserFactory that creates a domain and then loads a specific type - TypeParser - that is accessed cross-AppDomain in the parent domain:

public class TypeParserFactory : System.MarshalByRefObject,IDisposable    
{
/// <summary> /// TypeParser Factory method that loads the TypeParser /// object into a new AppDomain so it can be unloaded. /// Creates AppDomain and creates type. /// </summary> /// <returns></returns> public TypeParser CreateTypeParser() { if (!CreateAppDomain(null)) return null; /// Create the instance inside of the new AppDomain /// Note: remote domain uses local EXE's AppBasePath!!! TypeParser parser = null; try { Assembly assembly = Assembly.GetExecutingAssembly(); string assemblyPath = Assembly.GetExecutingAssembly().Location; parser = (TypeParser) this.LocalAppDomain.CreateInstanceFrom(assemblyPath, typeof(TypeParser).FullName).Unwrap(); } catch (Exception ex) { this.ErrorMessage = ex.GetBaseException().Message; return null; } return parser; } private bool CreateAppDomain(string lcAppDomain) { if (lcAppDomain == null) lcAppDomain = "wwReflection" + Guid.NewGuid().ToString().GetHashCode().ToString("x"); AppDomainSetup setup = new AppDomainSetup(); // *** Point at current directory setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; //setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"); this.LocalAppDomain = AppDomain.CreateDomain(lcAppDomain,null,setup); // Need a custom resolver so we can load assembly from non current path AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); return true; } …
}

Note that the classes must be either [Serializable] (by value) or inherit from MarshalByRefObject in order to be accessible remotely. Here I need to call methods on the remote object so all classes are MarshalByRefObject.

The specific problem code is the loading up a new type which points at an assembly that visible both in the current domain and the remote domain and then instantiates a type from it. This is the code in question:

Assembly assembly = Assembly.GetExecutingAssembly();               
string assemblyPath = Assembly.GetExecutingAssembly().Location;
parser = (TypeParser) this.LocalAppDomain.CreateInstanceFrom(assemblyPath,
                                       typeof(TypeParser).FullName).Unwrap();  

The last line of code is what blows up with the Unable to cast transparent proxy to type <type> error. Without the cast the code actually returns a TransparentProxy instance, but the cast is what blows up. In other words I AM in fact getting a TypeParser instance back but it can't be cast to the TypeParser type that is loaded in the current AppDomain.

Finding the Problem

To see what's going on I tried using the .NET 4.0 dynamic type on the result and lo and behold it worked with dynamic - the value returned is actually a TypeParser instance:

Assembly assembly = Assembly.GetExecutingAssembly();               
string assemblyPath = Assembly.GetExecutingAssembly().Location;
object objparser = this.LocalAppDomain.CreateInstanceFrom(assemblyPath,
                                      typeof(TypeParser).FullName).Unwrap();


// dynamic works
dynamic dynParser = objparser;
string info = dynParser.GetVersionInfo(); // method call works

// casting fails
parser = (TypeParser)objparser; 

So clearly a TypeParser type is coming back, but nevertheless it's not the right one. Hmmm… mysterious.
Another couple of tries reveal the problem however:

// works
dynamic dynParser = objparser;
string info = dynParser.GetVersionInfo(); // method call works

// c:\wwapps\wwhelp\wwReflection20.dll   (Current Execution Folder)
string info3 = typeof(TypeParser).Assembly.CodeBase;

// c:\program files\vfp9\wwReflection20.dll   (my COM client EXE's folder)
string info4 = dynParser.GetType().Assembly.CodeBase;

// fails
parser = (TypeParser)objparser; 

As you can see the second value is coming from a totally different assembly. Note that this is even though I EXPLICITLY SPECIFIED an assembly path to load the assembly from! Instead .NET decided to load the assembly from the original ApplicationBase folder. Ouch!

How I actually tracked this down was a little more tedious: I added a method like this to both the factory and the instance types and then compared notes:

public string GetVersionInfo()
{
    return ".NET Version: " + Environment.Version.ToString() + "\r\n" +
    "wwReflection Assembly: " + typeof(TypeParserFactory).Assembly.CodeBase.Replace("file:///", "").Replace("/", "\\") + "\r\n" +
    "Assembly Cur Dir: " + Directory.GetCurrentDirectory() + "\r\n" +
    "ApplicationBase: " + AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "\r\n" +
    "App Domain: " + AppDomain.CurrentDomain.FriendlyName + "\r\n";
}

For the factory I got:

.NET Version: 4.0.30319.239
wwReflection Assembly: c:\wwapps\wwhelp\bin\wwreflection20.dll
Assembly Cur Dir: c:\wwapps\wwhelp
ApplicationBase: C:\Programs\vfp9\
App Domain: wwReflection534cfa1f

For the instance type I got:

.NET Version: 4.0.30319.239
wwReflection Assembly: C:\\Programs\\vfp9\wwreflection20.dll
Assembly Cur Dir: c:\\wwapps\\wwhelp
ApplicationBase: C:\\Programs\\vfp9\
App Domain: wwDotNetBridge_56006605

which clearly shows the problem. You can see that both are loading from different appDomains but the each is loading the assembly from a different location.

Probably a better solution yet (for ANY kind of assembly loading problem) is to use the .NET Fusion Log Viewer to trace assembly loads.The Fusion viewer will show a load trace for each assembly loaded and where it's looking to find it. Here's what the viewer looks like:

FusionLogViewer

The last trace above that I found for the second wwReflection20 load (the one that is wonky) looks like this:

*** Assembly Binder Log Entry  (1/13/2012 @ 3:06:49 AM) ***

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

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework\V4.0.30319\clr.dll
Running under executable  c:\programs\vfp9\vfp9.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: User = Ras\ricks
LOG: DisplayName = wwReflection20, Version=4.61.0.0, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = file:///C:/Programs/vfp9/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = vfp9.exe
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: C:\Programs\vfp9\vfp9.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\V4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/Programs/vfp9/wwReflection20.DLL.
LOG: Assembly download was successful. Attempting setup of file: C:\Programs\vfp9\wwReflection20.dll
LOG: Entering run-from-source setup phase.
LOG: Assembly Name is: wwReflection20, Version=4.61.0.0, Culture=neutral, PublicKeyToken=null
LOG: Binding succeeds. Returns assembly from C:\Programs\vfp9\wwReflection20.dll.
LOG: Assembly is loaded in default load context.
WRN: The same assembly was loaded into multiple contexts of an application domain:
WRN: Context: Default | Domain ID: 2 | Assembly Name: wwReflection20, Version=4.61.0.0, Culture=neutral, PublicKeyToken=null
WRN: Context: LoadFrom | Domain ID: 2 | Assembly Name: wwReflection20, Version=4.61.0.0, Culture=neutral, PublicKeyToken=null
WRN: This might lead to runtime failures.
WRN: It is recommended to inspect your application on whether this is intentional or not.
WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.

Notice that the fusion log clearly shows that the .NET loader makes no attempt to even load the assembly from the path I explicitly specified.

Remember your Assembly Locations

As mentioned earlier all failures I've seen like this ultimately resulted from different versions of the same type being available in the two AppDomains. At first sight that seems ridiculous - how could the types be different and why would you have multiple assemblies - but there are actually a number of scenarios where it's quite possible to have multiple copies of the same assembly floating around in multiple places.

If you're hosting different environments (like hosting the Razor Engine, or ASP.NET Runtime for example) it's common to create a private BIN folder and it's important to make sure that there's no overlap of assemblies.

In my case of Html Help Builder the problem started because I'm using COM interop to access the .NET assembly and the above code. COM Interop has very specific requirements on where assemblies can be found and because I was mucking around with the loader code today, I ended up moving assemblies around to a new location for explicit loading. The explicit load works in the main AppDomain, but failed in the remote domain as I showed. The solution here was simple enough: Delete the extraneous assembly which was left around by accident.

Not a common problem, but one that when it bites is pretty nasty to figure out because it seems so unlikely that types wouldn't match. I know I've run into this a few times and writing this down hopefully will make me remember in the future rather than poking around again for an hour trying to debug the issue as I did today. Hopefully it'll save some of you some time as well in the future.

Posted in .NET  COM  

The Voices of Reason


 

Oisin G.
January 16, 2012

# re: Unable to cast transparent proxy to type &lt;type&gt;

A good start on the complexities of assembly identity, but it gets even hairier. It's also possible that the same assembly -from the same location - can be loaded multiple times into the same appdomain and be considered to have different identities. This is actually the case above too: you have wwReflection20 loaded in both the default and the loadfrom context. Suzanne Cook good wrote some really meaty posts about ten years ago covering all of the nuances in this interesting/frustrating area. Here's some link spam for your enjoyment:

Assembly Identity
http://blogs.msdn.com/b/suzcook/archive/2003/07/21/57232.aspx

LoadFile vs LoadFrom
http://blogs.msdn.com/b/suzcook/archive/2003/09/19/loadfile-vs-loadfrom.aspx

Choosing a Binding Context
http://blogs.msdn.com/b/suzcook/archive/2003/05/29/57143.aspx

Cheers

-Oisin

Rick Strahl
January 26, 2012

# re: Unable to cast transparent proxy to type &lt;type&gt;

@Oison - excellent resources! Oldies but goodies - the basics still matter!

John
May 05, 2013

# re: Unable to cast transparent proxy to type &lt;type&gt;

Hi Rick,

Thanks for the info. Am currently dealing with this problem now myself but could you clarify how the "AssemblyResolve" handler helps here. As pointed out by "CHOLAN" in your own article here:

http://www.west-wind.com/weblog/posts/2009/Jan/19/Assembly-Loading-across-AppDomains

Your handler is still loading the assembly on the current domain, though CHOLAN's solution to use the new domain doesn't seem to work either (but I'm new to this area so still researching things). Any assistance you can provide would be welcome. Thanks.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024