diff --git a/S7.Net.UnitTest/Helpers/TestClassWithArrays.cs b/S7.Net.UnitTest/Helpers/TestClassWithArrays.cs new file mode 100644 index 0000000..e3f1f31 --- /dev/null +++ b/S7.Net.UnitTest/Helpers/TestClassWithArrays.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace S7.UnitTest.Helpers +{ + public class TestClassWithArrays + { + public bool Bool { get; set; } + public bool[] BoolValues { get; set; } = new bool[2]; + public bool[] Fillers { get; set; } = new bool[13]; + + public short[] Shorts { get; set; } = new short[2]; + public ushort[] UShorts { get; set; } = new ushort[2]; + public int[] Ints { get; set; } = new int[2]; + public double[] Doubles { get; set; } = new double[2]; + + public short Short { get; set; } + public ushort UShort { get; set; } + public int Int { get; set; } + public double Double { get; set; } + } +} diff --git a/S7.Net.UnitTest/Helpers/TestClassWithCustomType.cs b/S7.Net.UnitTest/Helpers/TestClassWithCustomType.cs new file mode 100644 index 0000000..4255da7 --- /dev/null +++ b/S7.Net.UnitTest/Helpers/TestClassWithCustomType.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace S7.UnitTest.Helpers +{ + public class CustomType + { + public bool[] Bools { get; set; } = new bool[2]; + public bool[] Fillers { get; set; } = new bool[14]; + } + + public class TestClassWithCustomType + { + public int Int { get; set; } + public CustomType CustomType { get; set; } + public CustomType[] CustomTypes { get; set; } = new CustomType[2]; + } +} diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index cc2f72c..59a3494 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -56,6 +56,8 @@ + + diff --git a/S7.Net.UnitTest/S7NetTests.cs b/S7.Net.UnitTest/S7NetTests.cs index e7ca8d8..f034377 100644 --- a/S7.Net.UnitTest/S7NetTests.cs +++ b/S7.Net.UnitTest/S7NetTests.cs @@ -656,7 +656,7 @@ namespace S7.Net.UnitTest tc.DWordVariable = 850; plc.WriteClass(tc, DB2); - int expectedReadBytes = Types.Class.GetClassSize(tc.GetType()); + int expectedReadBytes = Types.Class.GetClassSize(tc); TestClass tc2 = new TestClass(); // Values that are read from a class are stored inside the class itself, that is passed by reference @@ -664,6 +664,77 @@ namespace S7.Net.UnitTest Assert.AreEqual(expectedReadBytes, actualReadBytes); } + + [TestMethod] + public void T22_ReadClassWithArray() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + TestClassWithArrays tc = new TestClassWithArrays(); + tc.Bool = true; + tc.BoolValues[1] = true; + tc.Int = int.MinValue; + tc.Ints[0] = int.MinValue; + tc.Ints[1] = int.MaxValue; + tc.Short = short.MinValue; + tc.Shorts[0] = short.MinValue; + tc.Shorts[1] = short.MaxValue; + tc.Double = float.MinValue; + tc.Doubles[0] = float.MinValue + 1; + tc.Doubles[1] = float.MaxValue; + tc.UShort = ushort.MinValue + 1; + tc.UShorts[0] = ushort.MinValue + 1; + tc.UShorts[1] = ushort.MaxValue; + + plc.WriteClass(tc, DB2); + TestClassWithArrays tc2 = plc.ReadClass(DB2); + + Assert.AreEqual(tc.Bool, tc2.Bool); + Assert.AreEqual(tc.BoolValues[0], tc2.BoolValues[0]); + Assert.AreEqual(tc.BoolValues[1], tc2.BoolValues[1]); + + Assert.AreEqual(tc.Int, tc2.Int); + Assert.AreEqual(tc.Ints[0], tc2.Ints[0]); + Assert.AreEqual(tc.Ints[1], tc.Ints[1]); + + Assert.AreEqual(tc.Short, tc2.Short); + Assert.AreEqual(tc.Shorts[0], tc2.Shorts[0]); + Assert.AreEqual(tc.Shorts[1], tc2.Shorts[1]); + + Assert.AreEqual(tc.Double, tc2.Double); + Assert.AreEqual(tc.Doubles[0], tc2.Doubles[0]); + Assert.AreEqual(tc.Doubles[1], tc2.Doubles[1]); + + Assert.AreEqual(tc.UShort, tc2.UShort); + Assert.AreEqual(tc.UShorts[0], tc2.UShorts[0]); + Assert.AreEqual(tc.UShorts[1], tc2.UShorts[1]); + } + + [TestMethod] + public void T22_ReadClassWithArrayAndCustomType() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + TestClassWithCustomType tc = new TestClassWithCustomType(); + tc.Int = int.MinValue; + tc.CustomType = new CustomType(); + tc.CustomType.Bools[1] = true; + tc.CustomTypes[0] = new CustomType(); + tc.CustomTypes[1] = new CustomType(); + tc.CustomTypes[0].Bools[0] = true; + tc.CustomTypes[1].Bools[1] = true; + + plc.WriteClass(tc, DB2); + TestClassWithCustomType tc2 = plc.ReadClass(DB2); + + Assert.AreEqual(tc.Int, tc2.Int); + Assert.AreEqual(tc.CustomType.Bools[0], tc2.CustomType.Bools[0]); + Assert.AreEqual(tc.CustomType.Bools[1], tc2.CustomType.Bools[1]); + Assert.AreEqual(tc.CustomTypes[0].Bools[0], tc2.CustomTypes[0].Bools[0]); + Assert.AreEqual(tc.CustomTypes[0].Bools[1], tc2.CustomTypes[0].Bools[1]); + Assert.AreEqual(tc.CustomTypes[1].Bools[0], tc2.CustomTypes[1].Bools[0]); + Assert.AreEqual(tc.CustomTypes[1].Bools[1], tc2.CustomTypes[1].Bools[1]); + } #endregion diff --git a/S7.Net/PLC.cs b/S7.Net/PLC.cs index 2df92aa..196e690 100644 --- a/S7.Net/PLC.cs +++ b/S7.Net/PLC.cs @@ -535,12 +535,16 @@ namespace S7.Net /// The number of read bytes public int ReadClass(object sourceClass, int db, int startByteAdr = 0) { - Type classType = sourceClass.GetType(); - int numBytes = Types.Class.GetClassSize(classType); + int numBytes = Types.Class.GetClassSize(sourceClass); + if(numBytes <= 0) + { + throw new Exception("The size of the class is less than 1 byte and therefore cannot be read"); + } + // now read the package var resultBytes = ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes); // and decode it - Types.Class.FromBytes(sourceClass, classType, resultBytes); + Types.Class.FromBytes(sourceClass, resultBytes); return resultBytes.Length; } diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 16c0266..0a97c7d 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -25,158 +25,268 @@ namespace S7.Net.Types } + private static double GetIncreasedNumberOfBytes(double startingNumberOfBytes, Type type) + { + double numBytes = startingNumberOfBytes; + + switch (type.Name) + { + case "Boolean": + numBytes += 0.125; + break; + case "Byte": + numBytes = Math.Ceiling(numBytes); + numBytes++; + break; + case "Int16": + case "UInt16": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + numBytes += 2; + break; + case "Int32": + case "UInt32": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + numBytes += 4; + break; + case "Float": + case "Double": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + numBytes += 4; + break; + default: + var propertyClass = Activator.CreateInstance(type); + numBytes += GetClassSize(propertyClass); + break; + } + + return numBytes; + } + /// /// Gets the size of the class in bytes. /// - /// the type of the class + /// An instance of the class /// the number of bytes - public static int GetClassSize(Type classType) + public static int GetClassSize(object instance) { double numBytes = 0.0; - var properties = GetAccessableProperties(classType); + var properties = GetAccessableProperties(instance.GetType()); foreach (var property in properties) { - switch (property.PropertyType.Name) + if (property.PropertyType.IsArray) { - case "Boolean": - numBytes += 0.125; - break; - case "Byte": - numBytes = Math.Ceiling(numBytes); - numBytes++; - break; - case "Int16": - case "UInt16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - numBytes += 2; - break; - case "Int32": - case "UInt32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - numBytes += 4; - break; - case "Float": - case "Double": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - numBytes += 4; - break; - default: - numBytes += GetClassSize(property.PropertyType); - break; + Type elementType = property.PropertyType.GetElementType(); + Array array = (Array)property.GetValue(instance, null); + if (array.Length <= 0) + { + throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero."); + } + + for (int i = 0; i < array.Length; i++) + { + numBytes = GetIncreasedNumberOfBytes(numBytes, elementType); + } + } + else + { + numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType); } } return (int)numBytes; } + private static object GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes) + { + object value = null; + + switch (propertyType.Name) + { + case "Boolean": + // get the value + int bytePos = (int)Math.Floor(numBytes); + int bitPos = (int)((numBytes - (double)bytePos) / 0.125); + if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0) + value = true; + else + value = false; + numBytes += 0.125; + break; + case "Byte": + numBytes = Math.Ceiling(numBytes); + value = (byte)(bytes[(int)numBytes]); + numBytes++; + break; + case "Int16": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // hier auswerten + ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); + value = source.ConvertToShort(); + numBytes += 2; + break; + case "UInt16": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // hier auswerten + value = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); + numBytes += 2; + break; + case "Int32": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // hier auswerten + uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3], + bytes[(int)numBytes + 2], + bytes[(int)numBytes + 1], + bytes[(int)numBytes + 0]); + value = sourceUInt.ConvertToInt(); + numBytes += 4; + break; + case "UInt32": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // hier auswerten + value = DWord.FromBytes( + bytes[(int)numBytes], + bytes[(int)numBytes + 1], + bytes[(int)numBytes + 2], + bytes[(int)numBytes + 3]); + numBytes += 4; + break; + case "Double": + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + // hier auswerten + value = Double.FromByteArray( + new byte[] { + bytes[(int)numBytes], + bytes[(int)numBytes + 1], + bytes[(int)numBytes + 2], + bytes[(int)numBytes + 3] }); + numBytes += 4; + break; + default: + var propClass = Activator.CreateInstance(propertyType); + var buffer = new byte[GetClassSize(propClass)]; + if (buffer.Length > 0) + { + Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length); + FromBytes(propClass, buffer); + value = propClass; + numBytes += buffer.Length; + } + break; + } + + return value; + } + /// /// Sets the object's values with the given array of bytes /// /// The object to fill in the given array of bytes - /// The class type /// The array of bytes - public static void FromBytes(object sourceClass, Type classType, byte[] bytes) + public static void FromBytes(object sourceClass, byte[] bytes) { if (bytes == null) return; - if (bytes.Length != GetClassSize(classType)) + if (bytes.Length != GetClassSize(sourceClass)) return; // and decode it - int bytePos = 0; - int bitPos = 0; double numBytes = 0.0; - - var properties = GetAccessableProperties(classType); + var properties = GetAccessableProperties(sourceClass.GetType()); foreach (var property in properties) { - switch (property.PropertyType.Name) + if (property.PropertyType.IsArray) { - case "Boolean": - // get the value - bytePos = (int)Math.Floor(numBytes); - bitPos = (int)((numBytes - (double)bytePos) / 0.125); - if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0) - property.SetValue(sourceClass, true, null); - else - property.SetValue(sourceClass, false, null); - numBytes += 0.125; - break; - case "Byte": - numBytes = Math.Ceiling(numBytes); - property.SetValue(sourceClass, (byte)(bytes[(int)numBytes]), null); - numBytes++; - break; - case "Int16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - // hier auswerten - ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); - property.SetValue(sourceClass, source.ConvertToShort(), null); - numBytes += 2; - break; - case "UInt16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - // hier auswerten - property.SetValue(sourceClass, Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]), null); - numBytes += 2; - break; - case "Int32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - // hier auswerten - uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3], - bytes[(int)numBytes + 2], - bytes[(int)numBytes + 1], - bytes[(int)numBytes + 0]); - property.SetValue(sourceClass, sourceUInt.ConvertToInt(), null); - numBytes += 4; - break; - case "UInt32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - // hier auswerten - property.SetValue(sourceClass, DWord.FromBytes(bytes[(int)numBytes], - bytes[(int)numBytes + 1], - bytes[(int)numBytes + 2], - bytes[(int)numBytes + 3]), null); - numBytes += 4; - break; - case "Double": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - // hier auswerten - property.SetValue(sourceClass, Double.FromByteArray(new byte[] { bytes[(int)numBytes], - bytes[(int)numBytes + 1], - bytes[(int)numBytes + 2], - bytes[(int)numBytes + 3] }), null); - numBytes += 4; - break; - default: - var buffer = new byte[GetClassSize(property.PropertyType)]; - if (buffer.Length == 0) - continue; - Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length); - var propClass = Activator.CreateInstance(property.PropertyType); - FromBytes(propClass, property.PropertyType, buffer); - property.SetValue(sourceClass, propClass, null); - numBytes += buffer.Length; - break; + Array array = (Array)property.GetValue(sourceClass, null); + Type elementType = property.PropertyType.GetElementType(); + for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) + { + array.SetValue( + GetPropertyValue(elementType, bytes, ref numBytes), + i); + } } + else + { + property.SetValue( + sourceClass, + GetPropertyValue(property.PropertyType, bytes, ref numBytes), + null); + } + } + } + + private static void ToBytes(Type propertyType, object propertyValue, byte[] bytes, ref double numBytes) + { + int bytePos = 0; + int bitPos = 0; + byte[] bytes2 = null; + + switch (propertyType.Name) + { + case "Boolean": + // get the value + bytePos = (int)Math.Floor(numBytes); + bitPos = (int)((numBytes - (double)bytePos) / 0.125); + if ((bool)propertyValue) + bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true + else + bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false + numBytes += 0.125; + break; + case "Byte": + numBytes = (int)Math.Ceiling(numBytes); + bytePos = (int)numBytes; + bytes[bytePos] = (byte)propertyValue; + numBytes++; + break; + case "Int16": + bytes2 = Int.ToByteArray((Int16)propertyValue); + break; + case "UInt16": + bytes2 = Word.ToByteArray((UInt16)propertyValue); + break; + case "Int32": + bytes2 = DInt.ToByteArray((Int32)propertyValue); + break; + case "UInt32": + bytes2 = DWord.ToByteArray((UInt32)propertyValue); + break; + case "Double": + bytes2 = Double.ToByteArray((double)propertyValue); + break; + default: + bytes2 = ToBytes(propertyValue); + break; + } + + if (bytes2 != null) + { + // add them + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + bytePos = (int)numBytes; + for (int bCnt = 0; bCnt < bytes2.Length; bCnt++) + bytes[bytePos + bCnt] = bytes2[bCnt]; + numBytes += bytes2.Length; } } @@ -187,67 +297,28 @@ namespace S7.Net.Types /// A byte array or null if fails. public static byte[] ToBytes(object sourceClass) { - Type type = sourceClass.GetType(); - - int size = GetClassSize(type); + int size = GetClassSize(sourceClass); byte[] bytes = new byte[size]; - byte[] bytes2 = null; - - int bytePos = 0; - int bitPos = 0; double numBytes = 0.0; var properties = GetAccessableProperties(sourceClass.GetType()); foreach (var property in properties) { - bytes2 = null; - switch (property.PropertyType.Name) + if (property.PropertyType.IsArray) { - case "Boolean": - // get the value - bytePos = (int)Math.Floor(numBytes); - bitPos = (int)((numBytes - (double)bytePos) / 0.125); - if ((bool)property.GetValue(sourceClass, null)) - bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true - else - bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false - numBytes += 0.125; - break; - case "Byte": - numBytes = (int)Math.Ceiling(numBytes); - bytePos = (int)numBytes; - bytes[bytePos] = (byte)property.GetValue(sourceClass, null); - numBytes++; - break; - case "Int16": - bytes2 = Int.ToByteArray((Int16)property.GetValue(sourceClass, null)); - break; - case "UInt16": - bytes2 = Word.ToByteArray((UInt16)property.GetValue(sourceClass, null)); - break; - case "Int32": - bytes2 = DInt.ToByteArray((Int32)property.GetValue(sourceClass, null)); - break; - case "UInt32": - bytes2 = DWord.ToByteArray((UInt32)property.GetValue(sourceClass, null)); - break; - case "Double": - bytes2 = Double.ToByteArray((double)property.GetValue(sourceClass, null)); - break; + Array array = (Array)property.GetValue(sourceClass, null); + Type elementType = property.PropertyType.GetElementType(); + for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) + { + ToBytes(elementType, array.GetValue(i), bytes, ref numBytes); + } } - if (bytes2 != null) + else { - // add them - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - bytePos = (int)numBytes; - for (int bCnt = 0; bCnt < bytes2.Length; bCnt++) - bytes[bytePos + bCnt] = bytes2[bCnt]; - numBytes += bytes2.Length; + ToBytes(property.PropertyType, property.GetValue(sourceClass, null), bytes, ref numBytes); } } return bytes; - } + } } }