DCSIMG
Assembly Private Bin Path Pitfall - All Your Base Are Belong To Us

All Your Base Are Belong To Us

Mostly .NET internals and other kinds of gory details

Assembly Private Bin Path Pitfall

When you configure a .NET AppDomain, one of the things you can set is the private probing path (relative to that AppDomain’s base directory) that will be used by the CLR loader when trying to load an assembly in that AppDomain. With that in mind, the following code is allegedly supposed to work, provided that the SecondAssembly assembly is indeed located in the PrivatePath subdirectory:

 class InOtherAppDomain : MarshalByRefObject 
 {
   public void SomeMethod()
   {
     AppDomain thisAD = AppDomain.CurrentDomain;
 
     Console.WriteLine(thisAD.BaseDirectory);
     Console.WriteLine(thisAD.RelativeSearchPath);
 
     string privateBinDirectory =
       thisAD.BaseDirectory + @"\\" + thisAD.RelativeSearchPath;
     Console.WriteLine(privateBinDirectory);
 
     Console.WriteLine(File.Exists(
       Path.Combine(privateBinDirectory, "SecondAssembly.dll" )));
 
     try 
     {
       Assembly.Load("SecondAssembly" );
       Console.WriteLine("Assembly loaded successfully" );
     }
     catch (Exception ex)
     {
       Console.WriteLine("Error loading assembly: " + ex.Message);
     }
   }
 }
 
 class Program 
 {
   static void Main(string[] args)
   {
     AppDomainSetup setup = new AppDomainSetup();
     setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
     setup.PrivateBinPath = @"\\PrivatePath";
 
     AppDomain secondAD = AppDomain.CreateDomain(
       "SecondAD", AppDomain.CurrentDomain.Evidence, setup);
 
     InOtherAppDomain obj = (InOtherAppDomain)
       secondAD.CreateInstanceAndUnwrap(
         typeof(InOtherAppDomain).Assembly.FullName,
         typeof(InOtherAppDomain).FullName);
 
     obj.SomeMethod();
   }
 }
 

Well, this program’s output is in fact the following:

D:\Development\Scratch\AssemblyPrivateBinPath\AssemblyPrivateBinPath\bin\Debug\
\PrivatePath
D:\Development\Scratch\AssemblyPrivateBinPath\AssemblyPrivateBinPath\bin\Debug\\\PrivatePath
True
Error loading assembly: Could not load file or assembly 'SecondAssembly' or one of its dependencies. The system cannot find the file specified.

Hmm. The secondary AppDomain’s base directory looks all right, as does the private probing path. The combined directory looks weird (the three slashes) but File.Exists says the assembly is there, so what’s with the exception?

If you examine the FusionLog property of the FileNotFoundException, you’ll find the following text (edited for brevity):

=== Pre-bind state information ===
LOG: User = Sasha-PC\Sasha
LOG: DisplayName = SecondAssembly
(Partial)
. . .
LOG: Appbase = file:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/
LOG: Initial PrivatePath = \PrivatePath
Calling assembly : AssemblyPrivateBinPath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
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:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/SecondAssembly.DLL.
LOG: Attempting download of new URL file:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/SecondAssembly/SecondAssembly.DLL.
LOG: Attempting download of new URL file:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/SecondAssembly.EXE.
LOG: Attempting download of new URL file:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/SecondAssembly/SecondAssembly.EXE.

Hmm. It’s not even looking in the PrivatePath subdirectory—it’s doing the default bind and nothing else. Well, there might be some additional information and the only way to get it is to enable the Fusion log.

There are several ways to enable the Fusion log, the easiest one is probably using the Assembly Binding Log Viewer (run fuslogvw.exe from an elevated Visual Studio command prompt). After enabling it and running the application again, I got the following bind information in the log output for SecondAssembly (edited for brevity):

*** Assembly Binder Log Entry  (5/6/2010 @ 3:32:20 PM) ***
The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.
. . . (same as earlier)
. . .
WRN: Not probing location file:///D:/PrivatePath/SecondAssembly.DLL, because the location falls outside of the appbase.
WRN: Not probing location file:///D:/PrivatePath/SecondAssembly/SecondAssembly.DLL, because the location falls outside of the appbase.
WRN: Not probing location file:///D:/PrivatePath/SecondAssembly.EXE, because the location falls outside of the appbase.
WRN: Not probing location file:///D:/PrivatePath/SecondAssembly/SecondAssembly.EXE, because the location falls outside of the appbase.
LOG: Attempting download of new URL file:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/SecondAssembly.DLL.
LOG: Attempting download of new URL file:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/SecondAssembly/SecondAssembly.DLL.
LOG: Attempting download of new URL file:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/SecondAssembly.EXE.
LOG: Attempting download of new URL file:///D:/Dev/AssemblyPrivateBinPath/bin/Debug/SecondAssembly/SecondAssembly.EXE.
LOG: All probing URLs attempted and failed.

Oh boy! It’s looking at D:\PrivatePath, which is outside the AppDomain’s base directory, and so it can’t be probed… But why would it go there if the directory is relative to the AppDomain’s base anyway??

Well, actually, I cheated here a little bit. When initializing privateBinDirectory in the above program, I didn’t use Path.Combine, but used direct string concatenation. And the reason is that if I used Path.Combine, the resulting combined directory would be . . . (dramatic suspense)

. . . (still dramatic suspense)

. . . (there could be more dramatic suspense here, but modern displays are really big and it’s really easy to scroll nowadays. Oh well, let’s give it away . . .)

\PrivatePath

Yes, that’s right, \PrivatePath. And thus the exception—if there’s a leading backslash in the beginning of the private probing path, the combined path is treated as an absolute path on the current volume, which is highly likely to fall outside the AppDomain’s base and cause this exception.

Comments

The Morning Brew - Chris Alcock » The Morning Brew #595 said:

Pingback from  The Morning Brew - Chris Alcock  » The Morning Brew #595

# May 7, 2010 11:32 AM

Dew Drop – May 7, 2010 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop – May 7, 2010 | Alvin Ashcraft's Morning Dew

# May 7, 2010 3:24 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: