CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Patrick Smacchia [MVP C#]


An unexpected CLR behavior : Loading 2 times the same assembly in an AppDomain

While developing a feature of NDepend we found out that the CLR can load 2 times the same assembly in an AppDomain.

 

The feature consists in including CQL constraints directly in the source code thanks to the attribute NDepend.CQL.CQLConstraintAttribute found in the NDepend.CQL.dll. This possibility represents a self-described way to express the architectural intentions and to make sure that your design remains clean. For example, in the following piece of code we make sure that we will be advised if the class NamedPipeHelper is used outside the classes ServerHost and ClientBase:


namepsace NDepend.AddIn.Common.InterProcessCommunication {

   [NDepend.CQL.CQLConstraint(@"// <Name>NamedPipeHelper restreint use</Name>

WARN IF Count > 0 IN SELECT TYPES WHERE

IsDirectlyUsing ""NDepend.AddIn.Common.InterProcessCommunication.NamedPipeHelper""

AND

!FullNameIs ""NDepend.AddIn.Common.InterProcessCommunication.ServerHost""

AND

!FullNameIs ""NDepend.AddIn.Common.InterProcessCommunication.ClientBase"" ")]

   public static class NamedPipeHelper {  }

}

 

We use both the framework System.Reflection and Mono.Cecil to analyze assemblies. Here is the code we used to extract CQLConstraint attributes on the type referenced by the variable typeReflection and then, to get an XML representation of the constraints:


System.Type typeReflection = null;

object[] listOfCQLConstraints =typeReflection.GetCustomAttributes(     typeof(NDepend.CQL.CQLConstraintAttribute), false);

string stringXml = (listOfCQLConstraints[0] as NDepend.CQL.CQLConstraintAttribute).ToXml();

// … here use stringXml

 

This code can lead to load 2 times the assembly NDepend.CQL.dll in the current AppDomain. Indeed, the JIT compiler triggers the load of $NDependInstallationPath$\Lib\NDepend.CQL.dll the first time it figures out that the type NDepend.CQL.CQLConstraintAttribute is used by a method. Then the call to the method Type.GetCustomAttributes(Type,bool) forces to load the assembly $assembliesAnalyzedPath$\NDepend.CQL.dll because it is the one that is referenced by the analyzed assemblies (the one that contains typeReflection). Here is a screenshot of the corresponding VisualStudio > Debug > Module window:

 

 

 

While this behavior seems reasonable because the 2 versions of NDepend.CQL.dll can be different (which is not the case here btw), it provokes a subtil bug in our code. There are 2 versions of the type CQLConstraintAttribute that are living in the AppDomain and the version we pass to the method Type.GetCustomAttributes(Type,bool) is not the same as the version that has been used to tag the types of the assemblies analyzed. Hence, the method Type.GetCustomAttributes(Type,bool) won’t return any CQLConstraintAttribute object.

 

Hopefully, we were able to correct this bug by using reflection. The idea is to fetch all attributes tagging an analyzed type with the method Type.GetCustomAttributes(bool) and then to find which attribute is tagged with a type named “NDepend.CQL.CQLConstraintAttribute”. Then we just try to obtain a method named “ToXml” and then we invoke it.  The extra bonus we get with this code is that it works even if the version of NDepend.CQL.dll that is referenced by the analyzed assembly is different than the version of NDepend.CQL.dll loaded from the NDepend installation assemblies.

 

object[] listOfCustomAttributes = typeReflection.GetCustomAttributes(false);

 

foreach (object obj in listOfCustomAttributes) {

   Type type = obj.GetType();

   if (type.FullName != "NDepend.CQL.CQLConstraintAttribute") { continue; }

 

   MethodInfo methodToXml = type.GetMethod("ToXml");

   if (methodToXml == null) { continue; }

 

   object objectStringXml = methodToXml.Invoke(obj, new object[] { });

   if (objectStringXml == null) { continue; }

 

   string stringXml = objectStringXml as string;

   if (stringXml == null) { continue; }

   // … here use stringXml

}

 

 

 

   



Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About Patrick Smacchia

Patrick Smacchia is a Visual C# MVP involved in software development for over 15 years. After graduating in mathematics and computer science, he has worked on software in a variety of fields including stock exchange, airline ticket reservation system as well as a satellite base station at Alcatel. He's currently a software consultant and trainer on .NET technologies as well as the lead developer of the tool NDepend which provides numerous metrics and caveats on any compiled .NET application. He is the author of Practical .NET2 and C#2, a .NET book conceived from real world experience with 647 compilable code listings. Check out Devlicio.us!