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;
- }
+ }
}
}