7

In my Visual Studio 2010 project, I use following Post-Build event command line to use sgen to create XmlSerializers.dll.

Post build event:

"$(ProgramFiles)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\sgen.exe" /a:"$(TargetPath)" /c:/keyfile:"c:\myproject\mykey.snk" /f

My project is strong named, so use the same key to strong name the "XmlSerializers.dll". VS creates the XmlSerializers.dll in output folder.

However, I have noticed using ProcessMonitor, .NET still invoke CSC.exe at runtime. I came accross this post, where the user had similar issue and resolved by using XmlSerializer(Type) constructor.

I used same technique in my code but it still invoke csc.exe:

var fs = new FileStream(SettingsFilePath, FileMode.Open);
try
{
var serializer = new XmlSerializer(typeof(AppSettings));
settings = (AppSettings)serializer.Deserialize(fs);
}
finally
{
fs.Close();
}

The reason I need to use precompiled XML serialisation, because of performance and also I have seen sometimes csc.exe erros on Windows shutdown. My application saves data when Form close, during shutdown, it fails because Windows will not allow a new process to start during the shutdown sequence. I have seen recommendations to get around this by precompiling XML serialisation.

Any suggestions about why XmlSerializer not using XmlSerializers.dll created by sgen?

Thanks.

Community
  • 1
  • 1
Din
  • 323
  • 3
  • 12
  • The simplest cure for the shutdown problem is to load the data at startup. Use fuslogvw.exe to check assembly resolution. – Hans Passant May 09 '12 at 09:41
  • Thanks, I need to save data (serialize to XML) when MainForm close. During normal close by the user is fine, no error, data serialized. However, if my application running and Windows shutdown then I see the CSC.exe errors. – Din May 10 '12 at 02:05

3 Answers3

4

Possiby the problem is different target platform: by default sgen uses 'Any CPU' (MSIL), if the assembly containing the type to be deserialized or serialized is compiled for x86 o x64, it won't load the .XmlSerializers.dll

More in general, I had a look at the .NET code that load the serialization assemblies - here is some code that reproduce the same behavior as a unit testing:

/// <summary>Generates an identifier for the assembly of a specified type</summary>
/// <remarks>Code copied from the .NET serialization classes - to emulate the same bahavior</remarks>
/// <param name="type">The type</param>
/// <returns>String identifying the type's assembly</returns>
static string GenerateAssemblyId(Type type) 
{ 
  Module[] modules = type.Assembly.GetModules();
  ArrayList list = new ArrayList();
  for (int i = 0; i < modules.Length; i++) {
    list.Add(modules[i].ModuleVersionId.ToString()); 
  }
  list.Sort(); 
  StringBuilder sb = new StringBuilder(); 
  for (int i = 0; i < list.Count; i++) {
    sb.Append(list[i].ToString()); 
    sb.Append(",");
  }
  return sb.ToString();
} // GenerateAssemblyId

/// <summary>Verifies that the serialization assembly for the specified type can be loaded</summary>
/// <remarks>Code copied from the .NET serialization classes - to emulate the same behavior and tests</remarks>
/// <param name="type">The type</param>
static void AssertCanLoadXmlSerializers(Type type)
{
  if (type == null)
    throw new ArgumentNullException("type");
  Assembly serializerAssembly = null;
  // Create the name of the XML serilizers assembly from the type's one
  AssemblyName name = type.Assembly.GetName(true); 
  name.Name = name.Name + ".XmlSerializers"; 
  name.CodeBase = null;
  name.CultureInfo = CultureInfo.InvariantCulture;
  try {
    serializerAssembly = Assembly.Load(name);
  } catch (Exception e) {
    Assert.Fail("Unable to load XML serialization assembly for type '{0}': {1}", type.FullName, e.Message);
  }
  object[] attrs = serializerAssembly.GetCustomAttributes(typeof(XmlSerializerVersionAttribute), false);
  if (attrs == null || attrs.Length == 0) {
    Assert.Fail(
      "Unable to use XML serialization assembly '{1}' for type '{0}': it does not contain XmlSerializerVersionAttribute", 
      type.FullName, 
      serializerAssembly.FullName
    );
  }
  if (attrs.Length > 1) {
    Assert.Fail(
      "Unable to use XML serialization assembly '{1}' for type '{0}': it contains multiple XmlSerializerVersionAttribute", 
      type.FullName, 
      serializerAssembly.FullName
    );
  }
  XmlSerializerVersionAttribute assemblyInfo = (XmlSerializerVersionAttribute)attrs[0];
  string assemblyId = GenerateAssemblyId(type);
  if (assemblyInfo.ParentAssemblyId != assemblyId) {
    Assert.Fail(
      "Unable to use XML serialization assembly '{1}' for type '{0}': it does not match assembly id '{2}'", 
      type.FullName, 
      serializerAssembly.FullName,
      assemblyId
    );
  }
} // AssertCanLoadXmlSerializers

simply call AssertCanLoadXmlSerializers() passing the type than needs to be serialized/deserialized. If the serialization assemblies do not load you can have a fairly good idea of why from the error messages.

I added it to our unit testing, so that I can be reasonably sure that the serialization assemblies are OK.

MiMo
  • 11,793
  • 1
  • 33
  • 48
  • This code is quite useful. Thank you. I am trying to understand how to correct the issue if the assertion fails on the last block ("it does not match assembly id"). Could you provide more explanation as to what that means and how to possibly go about fixing it? – Oren Hizkiya Aug 13 '15 at 16:25
  • It has been a while ... the assembly id is composed with the version numbers of the modules in that assembly, if they are different it would mean that the two assemblies (the one containing the type and the serialization one) are composed of different modules - or different versions of the same modules - so probably something changes after the serialization assembly has been created? Looking at the ids should give you a clue of what's different – MiMo Aug 13 '15 at 19:26
0

Have you tried running ngen.exe on the generated dll?

Running

ngen.exe install myproject.XmlSerializers.dll

will install a native image of the dll in the image cache, which should be used at runtime rather than invoking the JIT compiler.

tgriffin
  • 483
  • 1
  • 5
  • 13
  • Thanks, I have tried your suggestion of using ngen.exe, but still see cse.exe calls in ProcessMonitor. – Din May 10 '12 at 00:44
  • Here is screenshot of ProcessMonitor: http://screencast.com/t/iyKu79yv "DataSerializer.XmlSerializers.dll" is serializer dll created by sgen. – Din May 10 '12 at 00:55
0

Are you sure that the serialization assembly is correctly signed? Usually you need to escape the quotes. See here for more information.

You could also check if the IDs match. If you modify the source assembly after building the serialization assembly the IDs don't match any more and the serialization assembly won't be used. See here for more information.

If all that is correct: Disable Tools -> Options -> Debugging -> "Enable Just My Code" and enable Debug -> Excpetions -> Common Language Runtime Exceptions -> Thrown. Then debug your application to the point where the serialization is done. An first chance exception will be thrown stating why the serialization assembly can't be used.

cremor
  • 6,669
  • 1
  • 29
  • 72