Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Fails Only In Build, Not Editor - IXmlSerializable implementation

Discussion in 'Scripting' started by Lensy6, Jun 1, 2020.

  1. Lensy6

    Lensy6

    Joined:
    Jun 1, 2020
    Posts:
    5
    TL;DR - using an implementation of ixmlserializable fails only in builds, not the editor, and the error log message doesn't make sense.

    The game uses an implementation of IXmlSerializable as found in the top answer here https://stackoverflow.com/questions/20084/xml-serialization-and-inherited-types
    For ease of understanding here is the code as used
    Code (CSharp):
    1. using System;
    2. using System.Xml.Serialization;
    3.  
    4. namespace Utility.Xml
    5. {
    6.     public class AbstractXmlSerialiser<AbstractType> : IXmlSerializable
    7.     {
    8.         // Override the Implicit Conversions Since the XmlSerializer
    9.         // Casts to/from the required types implicitly.
    10.         public static implicit operator AbstractType(AbstractXmlSerialiser<AbstractType> o)
    11.         {
    12.             return o.Data;
    13.         }
    14.  
    15.         public static implicit operator AbstractXmlSerialiser<AbstractType>(AbstractType o)
    16.         {
    17.             return o == null ? null : new AbstractXmlSerialiser<AbstractType>(o);
    18.         }
    19.  
    20.         private AbstractType _data;
    21.         /// <summary>
    22.         /// [Concrete] Data to be stored/is stored as XML.
    23.         /// </summary>
    24.         public AbstractType Data
    25.         {
    26.             get { return _data; }
    27.             set { _data = value; }
    28.         }
    29.  
    30.         /// <summary>
    31.         /// **DO NOT USE** This is only added to enable XML Serialization.
    32.         /// </summary>
    33.         /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
    34.         public AbstractXmlSerialiser()
    35.         {
    36.             // Default Ctor (Required for Xml Serialization - DO NOT USE)
    37.         }
    38.  
    39.         /// <summary>
    40.         /// Initialises the Serializer to work with the given data.
    41.         /// </summary>
    42.         /// <param name="data">Concrete Object of the AbstractType Specified.</param>
    43.         public AbstractXmlSerialiser(AbstractType data)
    44.         {
    45.             _data = data;
    46.         }
    47.  
    48.         #region IXmlSerializable Members
    49.  
    50.         public System.Xml.Schema.XmlSchema GetSchema()
    51.         {
    52.             return null; // this is fine as schema is unknown.
    53.         }
    54.  
    55.         public void ReadXml(System.Xml.XmlReader reader)
    56.         {
    57.             // Cast the Data back from the Abstract Type.
    58.             string typeAttrib = reader.GetAttribute("type");
    59.  
    60.             // Ensure the Type was Specified
    61.             if (typeAttrib == null)
    62.                 throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
    63.                     "' because no 'type' attribute was specified in the XML.");
    64.  
    65.             Type type = Type.GetType(typeAttrib);
    66.  
    67.             // Check the Type is Found.
    68.             if (type == null)
    69.                 throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
    70.                     "' because the type specified in the XML was not found.");
    71.  
    72.             // Check the Type is a Subclass of the AbstractType.
    73.             if (!type.IsSubclassOf(typeof(AbstractType)))
    74.                 throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
    75.                     "' because the Type specified in the XML differs ('" + type.Name + "').");
    76.  
    77.             // Read the Data, Deserializing based on the (now known) concrete type.
    78.             reader.ReadStartElement();
    79.             this.Data = (AbstractType)new
    80.                 XmlSerializer(type).Deserialize(reader);
    81.             reader.ReadEndElement();
    82.         }
    83.  
    84.         public void WriteXml(System.Xml.XmlWriter writer)
    85.         {
    86.             // Write the Type Name to the XML Element as an Attrib and Serialize
    87.             Type type = _data.GetType();
    88.  
    89.             // BugFix: Assembly must be FQN since Types can/are external to current.
    90.             writer.WriteAttributeString("type", type.AssemblyQualifiedName);
    91.             new XmlSerializer(type).Serialize(writer, _data);
    92.         }
    93.  
    94.         #endregion
    95.     }
    96. }
    The code to use it is basically the same as a default XmlSerialiser with the addition of a type declaration in the Xml tagging whatsits

    Code (CSharp):
    1. public class Ability
    2. {
    3.     [XmlIgnore]
    4.     public Character thisCharacter;
    5.     public string description;
    6.     public string identifier;
    7.     [XmlArray("Effects")]
    8.     [XmlArrayItem("Effect", Type = typeof(AbstractXmlSerialiser<Effect>))]
    9.     public List<Effect> effects;
    10. ...
    And the container/serializer class

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.IO;
    3. using System.Text;
    4. using System.Xml.Serialization;
    5. using UnityEngine;
    6.  
    7. [XmlRoot("EnemyCollection")]
    8. public class EnemyContainer
    9. {
    10.     [XmlArray("Enemies"),XmlArrayItem("Enemy")]
    11.     public List<Enemy> Enemies = new List<Enemy>();
    12.  
    13.     private static XmlSerializer Serialiser
    14.     {
    15.         get
    16.         {
    17.             if (serialiser == null)
    18.             {
    19.                 Debug.Log(typeof(EnemyContainer) + " is type.");
    20.                 serialiser = new XmlSerializer(typeof(EnemyContainer)); //line getting error
    21.                 Debug.Log("created.");
    22.             }
    23.             return serialiser;
    24.         }
    25.     }
    26.     static XmlSerializer serialiser;
    27.  
    28.     public void Save(string path)
    29.     {
    30.         using (StreamWriter stream = new StreamWriter(path, false, Encoding.GetEncoding("UTF-8")))
    31.         {
    32.             Serialiser.Serialize(stream, this);
    33.         }
    34.     }
    35.  
    36.     public static EnemyContainer Load(string path)
    37.     {
    38.         using (StreamReader stream = new StreamReader(path, Encoding.GetEncoding("UTF-8")))
    39.         {
    40.             Debug.Log("Stream created.");
    41.             return Serialiser.Deserialize(stream) as EnemyContainer;
    42.         }
    43.     }
    44. }
    This all works perfectly in a program made entirely in visual studio and also works perfectly in the unity editor's play mode. However when used in a unity build it fails and the log file contains the following

    Code (CSharp):
    1. SystemException: Error running C:\Users\<User>\Desktop\mono\mini\mono.exe: The system cannot find the file specified.
    2.  
    3.   at Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch (System.CodeDom.Compiler.CompilerParameters options, System.String[] fileNames) [0x00160] in <ae22a4e8f83c41d69684ae7f557133d9>:0
    4.   at Microsoft.CSharp.CSharpCodeGenerator.FromSourceBatch (System.CodeDom.Compiler.CompilerParameters options, System.String[] sources) [0x00094] in <ae22a4e8f83c41d69684ae7f557133d9>:0
    5.   at Microsoft.CSharp.CSharpCodeGenerator.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromSourceBatch (System.CodeDom.Compiler.CompilerParameters options, System.String[] sources) [0x0000f] in <ae22a4e8f83c41d69684ae7f557133d9>:0
    6.   at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource (System.CodeDom.Compiler.CompilerParameters options, System.String[] sources) [0x00006] in <ae22a4e8f83c41d69684ae7f557133d9>:0
    7.   at System.Xml.Serialization.Compiler.Compile (System.Reflection.Assembly parent, System.String ns, System.Xml.Serialization.XmlSerializerCompilerParameters xmlParameters, System.Security.Policy.Evidence evidence) [0x00144] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
    8.   at System.Xml.Serialization.TempAssembly.GenerateAssembly (System.Xml.Serialization.XmlMapping[] xmlMappings, System.Type[] types, System.String defaultNamespace, System.Security.Policy.Evidence evidence, System.Xml.Serialization.XmlSerializerCompilerParameters parameters, System.Reflection.Assembly assembly, System.Collections.Hashtable assemblies) [0x004c2] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
    9.   at System.Xml.Serialization.TempAssembly..ctor (System.Xml.Serialization.XmlMapping[] xmlMappings, System.Type[] types, System.String defaultNamespace, System.String location, System.Security.Policy.Evidence evidence) [0x0006a] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
    10.   at System.Xml.Serialization.XmlSerializer.GenerateTempAssembly (System.Xml.Serialization.XmlMapping xmlMapping, System.Type type, System.String defaultNamespace) [0x0000e] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
    11.   at System.Xml.Serialization.XmlSerializer..ctor (System.Type type, System.String defaultNamespace) [0x000a9] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
    12.   at System.Xml.Serialization.XmlSerializer..ctor (System.Type type) [0x00000] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
    13.   at EnemyContainer.get_Serialiser () [0x00028] in C:\Users\<User>\Desktop\unity\My Adventure Party\Assets\Scripts\Enemies\EnemyContainer.cs:35
    14.   at EnemyContainer.Load (System.String path) [0x0001e] in C:\Users\<User>\Desktop\unity\My Adventure Party\Assets\Scripts\Enemies\EnemyContainer.cs:56
    15.   at NewTestAdventure.Start () [0x00093] in C:\Users\<User>\Desktop\unity\My Adventure Party\Assets\Scripts\NewAdventures\NewTestAdventure.cs:88
    Testing around I've discovered that the error only occurs when this implementation of IXmlSerializable is called for, when only using XmlSerializer as default it "works" but obviously cannot work out subclasses. I would definately like to just be able to use that and be done with it, but it would be a maintenance headache to have to statically declare all subclasses as you normally do with XmlSerialiser.
    This error also is present in different projects, i.e. it is not a project-specific problem. Stepping through and into the code I also know that the error is created at some point when creating the serializer but before reaching the abstractxmlserializer constructor.
    If anyone knows why it only has a problem in builds and not the editor/visual studio or any alternative that does work all help is apreciated.
     
  2. Cyber-Dog

    Cyber-Dog

    Joined:
    Sep 12, 2018
    Posts:
    352
    Does C:\Users\<User>\Desktop\mono\mini\mono.exe exist?
     
  3. Lensy6

    Lensy6

    Joined:
    Jun 1, 2020
    Posts:
    5
    Thanks for the response. That entire folder structure and file do not exist, and realistically it should not have some random folder seperate from the game's folder structure to work. Also from messing around the error always shows that the supposed desired folders are always 2 levels up from the game's directory (so putting the game directory directly in <user> changes the error to C:\Users\mono\mini\mono.exe).
     
  4. Cyber-Dog

    Cyber-Dog

    Joined:
    Sep 12, 2018
    Posts:
    352
    It’s a bit hard to work out from the code dump, I’m happy to dig into it if you share a cut down version of your project.

    Just a thought though, try running your build as admin. And see if the error persists and if the expected mono.exe path changes.
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,204
    So from the stack trace:

    Code (csharp):
    1.  
    2. at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource (System.CodeDom.Compiler.CompilerParameters options, System.String[] sources) [0x00006] in <ae22a4e8f83c41d69684ae7f557133d9>:0
    3.  
    I'm guessing that what's happening here is that the serializer is built through reflection in a way that's not compatible with AOT platforms. What platform are you on? IL2CPP is probably no going to handle runtime compilation very well. The same thing goes for any AOT platform (so consoles, IOS).
     
    Cyber-Dog likes this.
  6. Lensy6

    Lensy6

    Joined:
    Jun 1, 2020
    Posts:
    5
    Tried running as admin and the error stayed the same. Uploading to the forum said it's too large so here is a link https://mega.nz/file/zw8RwRyB#q8gruRLa-1l50_nBnGiwTpZWBQjzjvt9HF8iV4wjzQ4

    Doing at as standalone testing on a windows 7 64 bit machine. Also if I understand what you're asking it's set to Mono rather than IL2CPP backend.
     
    Cyber-Dog likes this.
  7. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,204
    Yes. Not necessarily permanently, but if it's something that Mono handles that IL2CPP doesn't, it's most probably a JIT vs. AOT issue.
     
  8. Lensy6

    Lensy6

    Joined:
    Jun 1, 2020
    Posts:
    5
    I don't quite understand what you mean. If IL2CPP can't handle something, wouldn't that not be a problem because it's not set to use that?
     
  9. Cyber-Dog

    Cyber-Dog

    Joined:
    Sep 12, 2018
    Posts:
    352

    Hey mate,

    Really sorry I didn't get back so soon, hadn't forgotten about you :)
    Was swamped with my own stuff, working on a unity plugin, MVC project and Blazor project all at the same time haha

    So from some investigation, it defiantly has something to do with the unity method of compiling with IL2CPP as @Baste mentioned. If you enable "Use incremental garbage collection" in project settings, then flick the scripting backend to mono and API compatibility to .net standard. It will pass, however, you will get new errors. I don't think the standard is fully supporting what you want though, that or similar issue your facing.

    I had a bastard of a problem with Mono when I wanted to uses system.darwing on OSX. I ended up having to copy some mono DLLs to manually override what unity was using.

    I suggest you build yourself a copy of MonoDevelop and try copying some files across where it expects them to be. It's a big pain, you will need VS which you already have and some other tools. All documents at Mono. Unfortunately, they only ship an installer for Linux and OSX. https://www.monodevelop.com/developers/building-monodevelop/

    Also, found this link here wich may be of some help.
    https://answers.unity.com/questions/364580/scripting-works-in-editor-try-it-but-not-build.html
    @aeroson mentions basically the same thing I did.

    Sorry, I don't have an actual fix, but if your persistent, then this may point you in the direction you need. Good luck!!!
     
    Lensy6 likes this.
  10. Lensy6

    Lensy6

    Joined:
    Jun 1, 2020
    Posts:
    5
    Thanks very much for looking, I'll try what you suggested and if that doesn't work I'll probably just look for some alternate approach. Cheers very much.