Issue with serialization (again)

I thought I had serialization licked but apparently not. I have one program that 1) creates some data (including 2 structures) in a class and serializes the class into a byte array, 2) deserializes the array so I can check what came out is the same as what went in, 3) writes the byte array to a file on the SD card and 4) reads the file and deserializes so I can check what got written to the file is the same as what I started with.

That program works OK and all my deserialize checks look like the original data.

When I try to run a separate program that has the same class definition and uses the same method to read from the file and deserialize, I get an "Unknown type exception when it tries to deserialize.

Any suggestions will be greatly appreciated since getting serialize/deserialize to work would be kind of a silver bullet for my current app.

Thanks - Gene

Here’s the program the creates the serialized file.


using System;
using Microsoft.SPOT;

using System.Text;
using System.IO;

using Microsoft.SPOT.IO;

using GHI.IO.Storage;

namespace CreateMission
{
    public class Program
    {
        [Serializable]
        public struct DepthTable
        {
            public float depth;
            public float PV;
            public byte sampleList;
            public TimeSpan parkTime;
        }

        [Serializable]
        public class fullMission
        {
            public bool recoveryMode;
            public TimeSpan samplePeriod;
            public DepthTable[] descendTable;
            public DepthTable[] ascendTable;
        }

        private static SDCard sdCard = new SDCard();
        private static VolumeInfo sdVol;

        public static void Main()
        {
            Debug.Print("Program Started");

            sdCard.Mount();
            sdVol = new VolumeInfo("SD");

            Debug.Print("Create the full mission");
            createMissionFile();
             
            Debug.Print(" ");
            Debug.Print("Load the mission file from disk");
            loadMission();
        }

        private static StringBuilder fileName = new StringBuilder(128);
        private static FileStream missionFile;

        public static StringBuilder missionFileName = new StringBuilder(128);

        public static void loadMission()
        {
            fileName.Clear();
            fileName.Append(sdVol.RootDirectory);
            fileName.Append("\\mission");
            fileName.Append("60m");
            fileName.Append(".mis");

            missionFile = new FileStream(fileName.ToString(), FileMode.Open, FileAccess.Read);
            byte[] fileBuffer = new byte[missionFile.Length];
            missionFile.Read(fileBuffer, 0, (int)missionFile.Length);
            fullMission missionFromFile = (fullMission)Reflection.Deserialize(fileBuffer, typeof(fullMission));

            missionFile.Close();
        }

        private static void createMissionFile()
        {
            fullMission mission = new fullMission();
            mission.recoveryMode = true;
            mission.samplePeriod = new TimeSpan(0, 1, 0);

            //First the descend table
            mission.descendTable = new DepthTable[1];
            mission.descendTable[0].depth = (float)60.0;   //decimeters
            mission.descendTable[0].PV = (float)0.10;      //mm per second
            mission.descendTable[0].parkTime = new TimeSpan(0, 10, 0);
            mission.descendTable[0].sampleList = (byte)7;

            //now cacluate the desired sample depths
            float[] sampleDepth = genSampleDepths();

            //fill out all the properties for each sampleDepth
            int depthNum = sampleDepth.Length;
            mission.ascendTable = new DepthTable[depthNum + 1];
            for (int i = 0; i < depthNum; i++)
            {
                mission.ascendTable[i].depth = sampleDepth[i];
                mission.ascendTable[i].PV = (float)0.10;
                mission.ascendTable[i].sampleList = (byte)7;
                mission.ascendTable[0].parkTime = new TimeSpan(0, 0, 0);
            }

            mission.ascendTable[depthNum].depth = (float)0.0;
            mission.ascendTable[depthNum].PV = (float)0.10;
            mission.ascendTable[depthNum].sampleList = (byte)0;
            mission.ascendTable[0].parkTime = new TimeSpan(0, 0, 0);
            
            //Serialize the mission
            byte[] buffer = Reflection.Serialize(mission, typeof(fullMission));

            //Check the serialization
            fullMission checkMission = (fullMission)Reflection.Deserialize(buffer, typeof(fullMission));

            //Debug.Print("Mission name = " + checkMission.missionName);
            Debug.Print("Num of ascend table elements = " + checkMission.ascendTable.Length.ToString());
            Debug.Print("Number of bytes in buffer = " + buffer.Length.ToString());

            //Write the mission to SD Card

            StringBuilder fileName = new StringBuilder(128);
            fileName.Clear();
            fileName.Append(sdVol.RootDirectory);
            fileName.Append("\\mission");
            fileName.Append("60m");
            fileName.Append(".mis");
            Debug.Print(fileName.ToString());

            FileStream missionFileWriter = new FileStream(fileName.ToString(), FileMode.Create, FileAccess.Write);

            missionFileWriter.Write(buffer, 0, buffer.Length);
            missionFileWriter.Close();

            //Read the mission back and check
            FileStream missionFileReader = new FileStream(fileName.ToString(), FileMode.Open, FileAccess.Read);
            byte[] fileBuffer = new byte[missionFileReader.Length];
            missionFileReader.Read(fileBuffer, 0, (int)missionFileReader.Length);

            //Deserialize here just to check
            fullMission checkMissionOnFile = (fullMission)Reflection.Deserialize(fileBuffer, typeof(fullMission));

            missionFileReader.Close();
        }

        private static float[] genSampleDepths()
        {
            int depthNum = 0;

            float[] sampleDepth = new float[500];

            float ascendDepth = (float)60.0;

            while (ascendDepth > 0)
            {
                sampleDepth[depthNum] = ascendDepth;
                depthNum = depthNum + 1;

                if (ascendDepth > 20)
                    ascendDepth = ascendDepth - 5;
                else
                    ascendDepth = ascendDepth - 2;
            }

            float[] returnArray = new float[depthNum];
            Array.Copy(sampleDepth, returnArray, depthNum);
            return (returnArray);
        }
    }
}


Here’s the program that tries to read the serialized file


using System;
using Microsoft.SPOT;

using System.Text;
using System.IO;

using Microsoft.SPOT.IO;

using GHI.IO.Storage;

namespace loadMission
{
    public class Program
    {
        [Serializable]
        public struct DepthTable
        {
            public float depth;
            public float PV;
            public byte sampleList;
            public TimeSpan parkTime;
        }

        [Serializable]
        public class fullMission
        {
            public bool recoveryMode;
            public TimeSpan samplePeriod;
            public DepthTable[] descendTable;
            public DepthTable[] ascendTable;
        }

        private static SDCard sdCard = new SDCard();
        private static VolumeInfo sdVol;

        public static void Main()
        {
            Debug.Print("Program Started");

            sdCard.Mount();
            sdVol = new VolumeInfo("SD");

            Debug.Print("Loading the mission file");
            loadMission();
        }

        private static StringBuilder fileName = new StringBuilder(128);
        private static FileStream missionFile;

        public static StringBuilder missionFileName = new StringBuilder(128);

        public static void loadMission()
        {
            fileName.Clear();
            fileName.Append(sdVol.RootDirectory);
            fileName.Append("\\mission");
            fileName.Append("60m");
            fileName.Append(".mis");

            missionFile = new FileStream(fileName.ToString(), FileMode.Open, FileAccess.Read);
            byte[] fileBuffer = new byte[missionFile.Length];
            missionFile.Read(fileBuffer, 0, (int)missionFile.Length);
            fullMission missionFromFile = (fullMission)Reflection.Deserialize(fileBuffer, typeof(fullMission));

            missionFile.Close();
        }
    }
}

Here’s the exception

#### Exception Microsoft.SPOT.UnknownTypeException - CLR_E_UNKNOWN_TYPE (1) ####
#### Message: 
#### Microsoft.SPOT.Reflection::Deserialize [IP: 0000] ####
#### loadMission.Program::loadMission [IP: 0068] ####
#### loadMission.Program::Main [IP: 0022] ####

A first chance exception of type ‘Microsoft.SPOT.UnknownTypeException’ occurred in Microsoft.SPOT.Native.dll
An unhandled exception of type ‘Microsoft.SPOT.UnknownTypeException’ occurred in Microsoft.SPOT.Native.dll

The timespan member does not seem to be serializable in .netmf (no serializable attribute) while it is the case in big .net.

@ leforban I so wish that solved my problem but sadly it didn’t. I commented out the TimeSpan in the structure and the class in both the createMission and the loadMission programs and still get the same unknown exception in the load program. I went so far as to comment out everything but the boolean in the class to be serialized and still get the same exception in the loadMission program.

The really confusing thing is everything including the TimeSpans and the DepthTable[] structures serializes and deserializes just fine from inside the createMission program that creates the class and stores the byte array to disk. It is just when I try to load the byte array from disk and deserialize in the loadMission program with what I think is the exact same class and structure definitions that I get the exception.

If anyone has an example that successfully serializes in one program and deserializes in a second program, I’d love to see it.

I’m really hoping someone has an answer to this issue since I really, really don’t want to write my own serializer/deserializer.

Thanks for looking at this - Gene

By the way, I’ve looked at the byte array serialized in the CreateMission program and the byte array read off the SD card in the loadMission program and they are identical so that isn’t the issue.

@ Gene -

Why not try JSON instead?

I am using XML or JSON for this. No need to wrestle with that (at least for me).

@ AWSOMEDEVSIGNER - I would have loved to use JSON but in this case every byte is incredibly expensive. The “mission” my programs refer to is a mission that gets downloaded from a computer on shore over the Iridium satellite network to a platform bobbing around and profiling up and down in the ocean with a suite of oceanographic research instruments. We’re using Iridium Short Burst Data (SBD) messages which have a maximum payload of 1890 bytes. Because my missions are mostly arrays of floating point values, the extra comma that JSON inserts really hurts. In fact, I may have to scale my floating point values into short integers just to get a long mission into 1 SBD.

However, it looks like I’ve got the problem licked which I’ll talk about in a subsequent post.

Thanks for your interest

Got it (I think). The solution turned out to be pretty easy although the path I went down to get there was incredibly painful.

The class I want to serialize has to be in a “Class Library” .dll, not just defined in each of the programs that serialize/deserialize. Maybe this is obvious to real programmers but it sure had me stumped for a couple of days. For those who don’t know how to do this, there is a template available when you create a new project in Visual Studio: FILE>New Project…>Templates>Visual C#>Micro Framework>Class Library. Put your class in the Program.cs file that VS creates, build (or rebuild) and then include the .dll that gets created in the bin directory of the Class Library project floder as a Reference in any project that needs to serialize/deserialize that class.

One benefit of the tortuous path I went down to solve this issue is I discovered you can write to the PC file system from inside the standard .Net Micro emulator. You can see how below.

Since .Net Micro is now the de-facto standard for all things IoT, I included my programs below in the hopes they will be useful to some bright, young, future entrepreneur who is going to create the next killer app that will solve all of the world’s problems.

Here’s the class library


using System;
using Microsoft.SPOT;

namespace MissionClass
{
    [Serializable]
    public struct DepthTable
    {
        public float depth;
        public float PV;
        public byte sampleList;
        public TimeSpan parkTime;
    }

    [Serializable]
    public class FullMission
    {
        public bool recoveryMode;
        public TimeSpan samplePeriod;
        public DepthTable[] descendTable;
        public DepthTable[] ascendTable;

        public FullMission()
        { }
    }
}

Here’s the MissionCreator app (most of it creates the mission, only a few lines are actually required to do the serialization)


using System;
using Microsoft.SPOT;

using System.IO;
using Microsoft.SPOT.IO;
using System.Text;

using MissionClass;

namespace MissionCreator
{
    public class Program
    {
        public static void Main()
        {
            Debug.Print("Program Started");

            FileStream logFile;

            FullMission mission = new FullMission();

            mission.recoveryMode = true;
            mission.samplePeriod = new TimeSpan(0, 1, 0);

            //First the descend table
            mission.descendTable = new DepthTable[1];
            mission.descendTable[0].depth = (float)60.0;   //decimeters
            mission.descendTable[0].PV = (float)0.10;      //mm per second
            mission.descendTable[0].parkTime = new TimeSpan(0, 10, 0);
            mission.descendTable[0].sampleList = (byte)7;

            //now cacluate the desired sample depths
            float[] sampleDepth = genSampleDepths();

            //fill out all the properties for each sampleDepth
            int depthNum = sampleDepth.Length;
            mission.ascendTable = new DepthTable[depthNum + 1];
            for (int i = 0; i < depthNum; i++)
            {
                mission.ascendTable[i].depth = sampleDepth[i];
                mission.ascendTable[i].PV = (float)0.10;
                mission.ascendTable[i].sampleList = (byte)7;
                mission.ascendTable[0].parkTime = new TimeSpan(0, 0, 0);
            }

            mission.ascendTable[depthNum].depth = (float)0.0;
            mission.ascendTable[depthNum].PV = (float)0.10;
            mission.ascendTable[depthNum].sampleList = (byte)0;
            mission.ascendTable[0].parkTime = new TimeSpan(0, 0, 0);

            StringBuilder sbTemp = new StringBuilder(64);

            sbTemp.Clear();
            sbTemp.Append(@ "\WINFS\testFile.001");
            logFile = new FileStream(sbTemp.ToString(), FileMode.Create, FileAccess.Write);

            byte[] buffer = Reflection.Serialize(mission, typeof(FullMission));
            logFile.Write(buffer, 0, buffer.Length);
            logFile.Close();
        }

        private static float[] genSampleDepths()
        {
            int depthNum = 0;

            float[] sampleDepth = new float[500];

            float ascendDepth = (float)60.0;

            while (ascendDepth > 0)
            {
                sampleDepth[depthNum] = ascendDepth;
                depthNum = depthNum + 1;

                if (ascendDepth > 20)
                    ascendDepth = ascendDepth - 5;
                else
                    ascendDepth = ascendDepth - 2;
            }

            float[] returnArray = new float[depthNum];
            Array.Copy(sampleDepth, returnArray, depthNum);
            return (returnArray);
        }
    }
}

Here’s the MissionReader app


using System;
using Microsoft.SPOT;

using System.IO;
using Microsoft.SPOT.IO;
using System.Text;

using MissionClass;

namespace MissionReader
{
    public class Program
    {
        public static void Main()
        {
            Debug.Print("Program Started");

            FileStream logFile;
            StringBuilder sbTemp = new StringBuilder(64);

            sbTemp.Clear();
            sbTemp.Append(@ "\WINFS\testFile.001");
            logFile = new FileStream(sbTemp.ToString(), FileMode.Open, FileAccess.Read);

            long fileLength = logFile.Length;
            byte[] buffer = new byte[(int)fileLength];

            logFile.Read(buffer, 0, (int)fileLength);

            FullMission newMission = new FullMission();

            newMission = (FullMission)Reflection.Deserialize(buffer, typeof(FullMission));

            logFile.Close();

            Debug.Print("New Mission recoveryMode = " + newMission.recoveryMode.ToString());
        }
    }
}

4 Likes

@ Gene - Sounds very interesting. Sure, in this case it does not make much sense. And congrats on solving the issue :slight_smile: