Alois Kraus

blog

  Home  |   Contact  |   Syndication    |   Login
  40 Posts | 8 Stories | 149 Comments | 165 Trackbacks

News



Article Categories

Archives

Post Categories

Image Galleries

Programming

Rules are meant to be bro.. ahem expanded. Especially if everybody tells you not to do so ;-). The Assembly Loader Rules are for example a very strict set of rules. One rule of the loader is that you cannot load assemblies from directories above the directory where the executable is located. This can cause some headaches if you want to share some assemblies across applications which are not installed inside the GAC. You can bypass this restriction by using Assembly.LoadFrom. A very good article about the how and when of Assembly.LoadFrom can be found at GotDotNet. If you have to deal with plugin architectures and loosely coupled components you will soon end up in a situation where you need to expand the default assembly loader rules. You can configure the loader to some extent via your App.Config file but you cannot configure a probing path for directories above your application binary location. Lets suppose you have an application lets call it App.exe which references ClassLibrary1.Dll  which in turn references ClassLibrary2.dll. Now you have to call Assembly.LoadFrom for ClassLibrary1.dll for some reason (e.g. it is a configured plugin). I have drawn the dependencies in the diagram below

UML Model



What will happen to your application domain when you try to load the same dll with LoadFrom after it has already been loaded because it was
referenced by the application? The following code illustrates this:


        class Program

    {

        static void Main(string[] args)

        {

          // Cause load of referenced ClassLibrary1.dll when entering this function

          // which contains the type ClassLibrary1.Class1

          ClassLibrary1.Class1 Instance = new ClassLibrary1.Class1();

 

          // Load ClassLibrary1.dll from another directory a second time but does NOT load

          // the referenced assembly ClassLibrary2.dll!

          Assembly assembly = Assembly.LoadFrom(

                   @"C:\Source\LoadedAssemblies\ClassLibrary1\bin\Debug\ClassLibrary1.dll");

 

          // Create another instance from the same type that is located in another assembly

          object obj = assembly.CreateInstance("ClassLibrary1.Class1");

 

          // Now we have ClassLibrary1.dll loaded two times into our AppDomain

          // Types loaded from different assemblies (even identical) cannot be cast to each other!

          // instance will always be null.

          ClassLibrary1.Class1 instanceFromOtherAssembly = obj as ClassLibrary1.Class1

        }

    }


The result is that ClassLibrary1.dll will be loaded into your AppDomain twice as you can easily check in the Debug->Windows->Modules window.



 

Loaded Modules


Loading the same assembly (same file size, same version, even binary identical) twice into an AppDomain is normally an error (at least I cannot think of a reason why you possible would like to do that). This can happen very easily when you have mis configured your plugins. These errors manifest in strange behavior and NullReferenceExceptions at weird places. The types from even binary identical assemblies are not the same for the CLR if the assembly is loaded multiple times into the same AppDomain. This results in the somewhat unexpected behavior of the following code fragment:

ClassLibrary1.Class1 instanceFromOtherAssembly = obj as ClassLibrary1.Class1;  // instance will always be null.

Ok this is something that should be avoided. Luckily we can check very easy if our AppDomain is in a healthy state by checking for double loaded assemblies after we
did load our plugins or extensions. The following utility class does exactly this. Check you AppDomain for double/multi loaded assemblies:
Addenum: JMT (I do not know his real name) was so kind to point out that I had an error in my algorithm which could miss some double loads. I have updated the source now. Good work JMT.


LoadedAssemblyChecker.cs

using System;

using System.Collections.Generic;

using System.Text;

using System.Reflection;

 

namespace Microsoft.Practices.EnterpriseLibrary.Configuration.Console

{

    /// <summary>

    /// Check if assemblies are loaded multiple times into a application domain. This is

    /// in most cases an error which is not so easy to find.

    /// </summary>

    public class LoadedAssemblyChecker

    {

        class AssemblyInfo

        {

            string myName;

 

            public string Name

            {

                get { return myName; }

            }

 

            string myLocation;

 

            public string Location

            {

                get { return myLocation; }

            }

 

            public AssemblyInfo(string name, string location)

            {

                myName = name;

                myLocation = location;

            }

        }

 

        /// <summary>

        /// Check the current Application domain if there are assemblies loaded multiple times.

        /// </summary>

        /// <returns>Dictionary which contains as key the assembly name which is loaded multiple times and as key

        /// as string list with the locations from where it was loaded.When no conflict has

        /// been determined an emtpy list is returned.</returns>

        public static IDictionary<string, IList<string>> GetConflictingAssemblies()

        {

            Assembly[] ass = AppDomain.CurrentDomain.GetAssemblies();

            List<AssemblyInfo> TotalList = new List<AssemblyInfo>();

 

            foreach (Assembly assembly in ass)

            {

                AssemblyInfo info = new AssemblyInfo(assembly.GetName().Name, assembly.CodeBase);

                TotalList.Add(info);

            }

 

            Dictionary<string, IList<string>> ConflictingList = new Dictionary<string, IList<string>>();

 

            AssemblyInfo curInfo;

            AssemblyInfo secondInfo;

            for (int i = 0; i < TotalList.Count; i++)

            {

                curInfo = TotalList[i];

                for (int j = i + 1; j < TotalList.Count; )

                {

                    secondInfo = TotalList[j];

                    if (String.Compare(curInfo.Name, secondInfo.Name, true) == 0)

                    {

                        if (!ConflictingList.ContainsKey(curInfo.Name))

                        {

                            List<string> Locations = new List<string>();

                            ConflictingList.Add(curInfo.Name, Locations);

                            ConflictingList[curInfo.Name].Add(curInfo.Location);

                        }

                        ConflictingList[curInfo.Name].Add(secondInfo.Location);

                        TotalList.Remove(secondInfo);

                    }

                    else j++;

                }

 

            }

 

            return ConflictingList;

        }

    }

}


With this little helper class you can easily identify the conflicts and warn the user after you have loaded all avialiable plugins:

            IDictionary<string, IList<string>> list = LoadedAssemblyChecker.GetConflictingAssemblies();

            foreach (KeyValuePair<string,IList<string> > info in list)

            {

                Console.WriteLine("Detected multiple load of assembly {0} from ",info.Key);

                foreach(string Location in info.Value)

                {

                    Console.WriteLine("\t{0}", Location);

                }

            }

Expected Output:

Detected multiple load of assembly ClassLibrary1 from
        file:///C:/Source/LoadedAssemblies/LoadedAssemblies/bin/Debug/ClassLibrary1.DLL
        file:///C:/Source/LoadedAssemblies/ClassLibrary1/bin/Debug/ClassLibrary1.dll

This mechanism is employed in my extension to the Enterprise Library Configuration Tool which allows you to configure where the assembly loader should look for plugins and their dependent assemblies. Assembly loading can be a tricky business if you do not know the rules. I hope this explains at least some of the oddities that can happen to your application during development. This type of checker is very valuable to find debug/release mismatches where one half of your app uses the debug version of an assembly and the rest uses the release version. This little class can help you a lot to diagnose deployment errors at runtime inside your application.

posted on Tuesday, April 04, 2006 9:35 PM

Feedback

# re: Assembly.LoadFrom demystified 4/11/2006 11:11 PM JMT
Hello Alois,

It seems to me there are some errors in your nested loop. First, currInfo.Location gets added to ConflictingList every time a match is found. Second, you are removing items from TotalList while also incrementing your iterator 'j' which causes the inner loop to skip items; in some cases this does not cause a problem since you start the inner loop from 0 each time, but there are some cases where duplicates will be missed. Here are some corrections to the inner loop that will solve these problems:

for (int j = i+1; j < TotalList.Count; )
{
secondInfo = TotalList[j];
if (String.Compare(curInfo.Name, secondInfo.Name, true) == 0)
{
if (!ConflictingList.ContainsKey(curInfo.Name))
{
List<string> Locations = new List<string>();
ConflictingList.Add(curInfo.Name, Locations);
ConflictingList[curInfo.Name].Add(curInfo.Location);
}
ConflictingList[curInfo.Name].Add(secondInfo.Location);
TotalList.Remove(secondInfo);
}
else j++;
}


And while I have the chance, I should say thanks for all the informative blogs, great work

# re: Assembly.LoadFrom demystified 5/3/2006 7:20 PM foo
think of this not as an error but as a feature. if you have strong typed assemblies you maybe even want to have assemblies which are different components to be located twice with different version within your process.

# re: Assembly.LoadFrom demystified 5/3/2006 8:19 PM Alois Kraus
Yes I am aware of versioning scenarios that might be possible. But to be honest I doubt I will ever see that in a real software package. It is really very tricky to do this in a realiable manner. Beside that Assembly.LoadFrom will not allow this because it does load an assembly only once into an AppDomain even if you called it serveral times.

Yours,
Alois Kraus

# re: Assembly.LoadFrom demystified 1/24/2009 11:55 AM Per Millard
I can't see that this checker is very useable. In fact, you could have 2 different assemblies which just happends to have the same name ... as you compare the assemblies by name, you can not be absolutely sure that you have an error

# re: Assembly.LoadFrom demystified 1/25/2009 12:17 AM Alois Kraus
No check is 100% but I am fine with a 99% detection rate.

Yours,
Alois Kraus


# re: Assembly.LoadFrom demystified 5/21/2009 7:22 AM Daniel
Good article. I'm having a problem with that fucking assembly loader rules.

I need to develop a windows service who needs to load some assemblies in memory.

First i'm using an simulator in console application, for tests, and after i will develop the windows service.

But what happens? In this console application i added references for some class libraries that i create for tests in the same solution.

So when i load the assemblies in the current domain, those libraries are not loaded, only the native libraries of .Net framework are loaded, and the classes of the project in execution.

Look below the method that i having problems:


public void PublicarClasses()
{

try
{

TcpChannel channel = new TcpChannel(1200);


ChannelServices.RegisterChannel(channel);


AppDomain domain = AppDomain.CurrentDomain;


Evidence asEvidence = domain.Evidence;


Assembly[] assembly = domain.GetAssemblies();


foreach (Assembly assem in assembly)
{

Module[] mod = assem.GetModules(false);

foreach (Module md in mod)
{

Type[] tipos = md.GetTypes();

tipos = BlackList(tipos);

foreach (Type t in tipos)
{

if (t.IsSubclassOf(typeof(MarshalByRefObject)))
{
Console.WriteLine(t.ToString());

RemotingConfiguration.RegisterWellKnownServiceType(t.GetType(), t.Name, WellKnownObjectMode.Singleton);

}

}

}

}


Console.WriteLine("\nListening port 1200...");
Console.ReadLine();

}

catch (Exception ex)
{
throw ex;
}
}

The problem happens when i execute the domain.GetAssemblies();
How i said before, the external class libraries are not loaded, and in my console application create copies of the dll's on his Bin folder.
I don't understand that shit, someone know what's happen?

Thanks,

Daniel

# re: Assembly.LoadFrom demystified 5/23/2009 6:20 PM Alois Kraus
The code you did show does not load any assemblies. If you want to look for assembly loading failures you can use Fusion Log (fuslogvw.exe from the .NET Framework SDK). It ca be started from a Visual Studio command prompt which is under the Tools directory in the start menu inside the Microsoft Visual Studio 200x folder.
Some reads about fusion log:

http://msdn.microsoft.com/en-us/library/e74a18c4(vs.71).aspx

Yours,
Alois Kraus


Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: