I’m trying to use Mono.Cecil to do a very straight-forward and simple
comparison of two assemblies to see if anything has changed. Since we
compile our assemblies over and over, the internal “version” changes,
but this doesn’t mean the code has changed.
I thought I had it running properly, iterating through Types, then
checking the hash for each MethodDefinition, PropertyDefinition, and
FieldDefinition, but something’s not right. For an assembly that has
no changes, I’m getting different hash tags.
My guess is that some items come back with a signature from
instantiation, and this signature is impacting the hash. I’m also
picking up things like “PrivateImplementation” that are identical, but
have GUIDS that make them not identical. How can I skip this properly?
My code is below…if anyone could give me a hint on what I may be
missing, I’d appreciate it…
The code should return 1 for changed files, 2 for unchanged files.
Version changes should be ignored, but any field, property, or method
change (including contents of methods and properties) should be
reported.
Thanks,
David C.
* * * * *
public static int Compare(string contentName, string file1,
string file2, ref StringBuilder report)
{
report.Append("Checking '" + contentName + "': ");
//
// If the file is an executable or assembly...
//
if (file1.EndsWith(".dll") || file1.EndsWith(".exe"))
{
AssemblyDefinition def1 = null;
AssemblyDefinition def2 = null;
try
{
def1 = AssemblyDefinition.ReadAssembly(file1);
def2 = AssemblyDefinition.ReadAssembly(file2);
}
catch (Exception ex)
{
Debug.WriteLine("Assemblies cannot be loaded. " +
ex.Message);
return -1;
}
List<TypeDefinition> typedef1 = null;
try
{
typedef1 =
def1.MainModule.Types.ToList<TypeDefinition>();
}
catch (Exception ex)
{
Debug.WriteLine("Cannot load types for '" +
def1.Name + " - " + ex.Message);
return -1;
}
List<TypeDefinition> typedef2 = null;
try
{
typedef2 =
def2.MainModule.Types.ToList<TypeDefinition>();
}
catch (Exception ex)
{
Debug.WriteLine("Cannot load types for '" +
def2.Name + " - " + ex.Message);
return -1;
}
if (typedef1.Count != typedef2.Count)
{
report.AppendLine("Unequal number of internal
types.\n");
return 1;
}
TypeDefinition[] file1Types = typedef1.ToArray();
TypeDefinition[] file2Types = typedef2.ToArray();
Array.Sort(file1Types, delegate(TypeDefinition type1,
TypeDefinition type2)
{
return type1.FullName.CompareTo(type2.FullName);
});
Array.Sort(file2Types, delegate(TypeDefinition type1,
TypeDefinition type2)
{
return type1.FullName.CompareTo(type2.FullName);
});
for (int t = 0; t < file1Types.Length; t++)
{
TypeDefinition type1 = file1Types[t];
TypeDefinition type2 = file2Types[t];
#region Test methods
List<MethodDefinition> members1 =
type1.Methods.ToList<MethodDefinition>();
List<MethodDefinition> members2 =
type2.Methods.ToList<MethodDefinition>();
MethodDefinition[] marray1 = members1.ToArray();
MethodDefinition[] marray2 = members2.ToArray();
Array.Sort(marray1, delegate(MethodDefinition m1,
MethodDefinition m2)
{
return m1.FullName.CompareTo(m2.FullName);
});
Array.Sort(marray2, delegate(MethodDefinition m1,
MethodDefinition m2)
{
return m1.FullName.CompareTo(m2.FullName);
});
if (marray1.Length != marray2.Length)
{
foreach (MethodDefinition method in marray1)
{
if (!
marray2.Contains<MethodDefinition>(method))
{
report.Append(" " + method.Name + " is
in old file, but not in new file.");
}
}
foreach (MethodDefinition method in marray2)
{
if (!
marray1.Contains<MethodDefinition>(method))
{
report.Append(" " + method.Name + " is
in new file, but not in old file.");
}
}
report.AppendLine("\n\n");
return 1;
}
for (int m = 0; m < marray1.Length; m++)
{
MethodDefinition m1 = marray1[m];
MethodDefinition m2 = marray2[m];
if (m1.GetHashCode() != m2.GetHashCode())
{
report.AppendLine("Code changed for method
'" + m1.FullName + "' in type '" + type1.FullName + "'.\n");
return 1;
}
}
#endregion
#region Test Properties
List<PropertyDefinition> props1 =
type1.Properties.ToList<PropertyDefinition>();
List<PropertyDefinition> props2 =
type2.Properties.ToList<PropertyDefinition>();
if (props1.Count != props2.Count)
{
report.AppendLine("Unequal number of
properties for type '" + type1.FullName + "'.\n");
return 1;
}
PropertyDefinition[] parray1 = props1.ToArray();
PropertyDefinition[] parray2 = props2.ToArray();
Array.Sort(parray1, delegate(PropertyDefinition
p1, PropertyDefinition p2)
{
return p1.FullName.CompareTo(p2.FullName);
});
Array.Sort(parray2, delegate(PropertyDefinition
p1, PropertyDefinition p2)
{
return p1.FullName.CompareTo(p2.FullName);
});
for (int p = 0; p < parray1.Length; p++)
{
PropertyDefinition p1 = parray1[p];
PropertyDefinition p2 = parray2[p];
if (p1.GetHashCode() != p2.GetHashCode())
{
report.AppendLine("Code changed for
property '" + p1.FullName + "' in type '" + type1.FullName + "'.\n");
return 1;
}
}
#endregion
#region Test Fields
List<FieldDefinition> fields1 =
type1.Fields.ToList<FieldDefinition>();
List<FieldDefinition> fields2 =
type2.Fields.ToList<FieldDefinition>();
if (fields1.Count != fields2.Count)
{
report.AppendLine("Unequal number of internal
fields for type '" + type1.FullName + "'.\n");
return 1;
}
FieldDefinition[] farray1 = fields1.ToArray();
FieldDefinition[] farray2 = fields2.ToArray();
Array.Sort(farray1, delegate(FieldDefinition f1,
FieldDefinition f2)
{
return f1.FullName.CompareTo(f2.FullName);
});
Array.Sort(farray2, delegate(FieldDefinition f1,
FieldDefinition f2)
{
return f1.FullName.CompareTo(f2.FullName);
});
for (int f = 0; f < farray1.Length; f++)
{
FieldDefinition f1 = farray1[f];
FieldDefinition f2 = farray2[f];
if (f1.GetHashCode() != f2.GetHashCode() && (!
f1.FullName.Contains("PrivateImplementation")))
{
report.AppendLine("Code changed for field
'" + f1.FullName + "' in type '" + type1.FullName + "'.\n");
return 1;
}
}
#endregion
}
}
else
{
//
// We're only getting the hash and comparing...
//
string hashFile1 = GetMD5Hash(file1);
string hashFile2 = GetMD5Hash(file2);
if (hashFile1 != hashFile2)
{
report.AppendLine("Content has changed.\n");
return 1;
}
}
report.AppendLine("Files are identical.\n");
return 2;
}
public static string GetMD5Hash(string pathName)
{
string strResult = "";
string strHashData = "";
byte[] arrbytHashValue;
System.IO.FileStream oFileStream = null;
System.Security.Cryptography.MD5CryptoServiceProvider
oMD5Hasher =
new
System.Security.Cryptography.MD5CryptoServiceProvider();
try
{
oFileStream = GetFileStream(pathName);
arrbytHashValue = oMD5Hasher.ComputeHash(oFileStream);
oFileStream.Close();
strHashData =
System.BitConverter.ToString(arrbytHashValue);
strHashData = strHashData.Replace("-", "");
strResult = strHashData;
}
catch (System.Exception ex)
{
strResult = "";
}
return (strResult);
}
private static FileStream GetFileStream(string pathName)
{
return (new FileStream(pathName, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite));
}
--
--
mono-cecil