diff --git a/S7.Net.UnitTest/Helpers/S7TestServer.cs b/S7.Net.UnitTest/Helpers/S7TestServer.cs index e8d6cf4..8db9328 100644 --- a/S7.Net.UnitTest/Helpers/S7TestServer.cs +++ b/S7.Net.UnitTest/Helpers/S7TestServer.cs @@ -9,6 +9,7 @@ namespace S7.Net.UnitTest.Helpers static private byte[] DB1 = new byte[1024]; // Our DB1 static private byte[] DB2 = new byte[64000]; // Our DB2 static private byte[] DB3 = new byte[1024]; // Our DB3 + static private byte[] DB4 = new byte[6] { 3, 128, 1, 0, 197, 104 }; // Our DB4 private static S7Server.TSrvCallback TheEventCallBack; // <== Static var containig the callback private static S7Server.TSrvCallback TheReadCallBack; // <== Static var containig the callback @@ -36,9 +37,10 @@ namespace S7.Net.UnitTest.Helpers 1, // Its number is 1 (DB1) DB1, // Our buffer for DB1 DB1.Length); // Its size - // Do the same for DB2 and DB3 + // Do the same for DB2, DB3, and DB4 Server.RegisterArea(S7Server.srvAreaDB, 2, DB2, DB2.Length); Server.RegisterArea(S7Server.srvAreaDB, 3, DB3, DB3.Length); + Server.RegisterArea(S7Server.srvAreaDB, 4, DB4, DB4.Length); // Exclude read event to avoid the double report // Set the callbacks (using the static var to avoid the garbage collect) diff --git a/S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs b/S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs new file mode 100644 index 0000000..0d5d417 --- /dev/null +++ b/S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace S7.Net.UnitTest.Helpers +{ + class TestClassInnerWithBool + { + public bool BitVariable00 { get; set; } + } + + class TestClassInnerWithByte + { + public byte ByteVariable00 { get; set; } + } + + class TestClassInnerWithShort + { + public short ShortVarialbe00 { get; set; } + } + + class TestClassWithNestedClass + { + /// + /// DB1.DBX0.0 + /// + public bool BitVariable00 { get; set; } + + /// + /// DB1.DBX0.1 + /// + public TestClassInnerWithBool BitVariable01 { get; set; } = new TestClassInnerWithBool(); + + /// + /// DB1.DBB1.0 + /// + public TestClassInnerWithByte ByteVariable02 { get; set; } = new TestClassInnerWithByte(); + + /// + /// DB1.DBX2.0 + /// + public bool BitVariable03 { get; set; } + + /// + /// DB1.DBW4 + /// + public TestClassInnerWithShort ShortVariable04 { get; set; } = new TestClassInnerWithShort(); + } +} diff --git a/S7.Net.UnitTest/S7.Net.UnitTest.csproj b/S7.Net.UnitTest/S7.Net.UnitTest.csproj index cbe6c47..dabda6d 100644 --- a/S7.Net.UnitTest/S7.Net.UnitTest.csproj +++ b/S7.Net.UnitTest/S7.Net.UnitTest.csproj @@ -62,6 +62,7 @@ + diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index 0adb91b..31f99eb 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -663,6 +663,28 @@ namespace S7.Net.UnitTest } } + [TestMethod] + public async Task Test_Async_ReadClassWithNestedClassAfterBit() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + Assert.AreEqual(6, Types.Class.GetClassSize(new TestClassWithNestedClass())); + + TestClassWithNestedClass tc = new TestClassWithNestedClass(); + tc.BitVariable00 = true; + tc.BitVariable01.BitVariable00 = true; + tc.ByteVariable02.ByteVariable00 = 128; + tc.BitVariable03 = true; + tc.ShortVariable04.ShortVarialbe00 = -15000; + + TestClassWithNestedClass tc2 = await plc.ReadClassAsync(DB4); + Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00); + Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00); + Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00); + Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03); + Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00); + } + [TestMethod] [ExpectedException(typeof(NullReferenceException))] public async Task Test_Async_ReadStructThrowsExceptionPlcIsNotConnected() @@ -737,7 +759,7 @@ namespace S7.Net.UnitTest }; plc.WriteClass(tc, DB2); - int expectedReadBytes = Types.Class.GetClassSize(tc); + int expectedReadBytes = (int)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 diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index a3b01c9..1b648f2 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -40,6 +40,7 @@ namespace S7.Net.UnitTest { #region Constants const int DB2 = 2; + const int DB4 = 4; #endregion #region Private fields @@ -694,6 +695,29 @@ namespace S7.Net.UnitTest } } + [TestMethod] + public void T31_ReadClassWithNestedClassAfterBit() + { + Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor."); + + Assert.AreEqual(6, Types.Class.GetClassSize(new TestClassWithNestedClass())); + + TestClassWithNestedClass tc = new TestClassWithNestedClass(); + tc.BitVariable00 = true; + tc.BitVariable01.BitVariable00 = true; + tc.ByteVariable02.ByteVariable00 = 128; + tc.BitVariable03 = true; + tc.ShortVariable04.ShortVarialbe00 = -15000; + + TestClassWithNestedClass tc2 = new TestClassWithNestedClass(); + plc.ReadClass(tc2, DB4); + Assert.AreEqual(tc.BitVariable00, tc2.BitVariable00); + Assert.AreEqual(tc.BitVariable01.BitVariable00, tc2.BitVariable01.BitVariable00); + Assert.AreEqual(tc.ByteVariable02.ByteVariable00, tc2.ByteVariable02.ByteVariable00); + Assert.AreEqual(tc.BitVariable03, tc2.BitVariable03); + Assert.AreEqual(tc.ShortVariable04.ShortVarialbe00, tc2.ShortVariable04.ShortVarialbe00); + } + [TestMethod, ExpectedException(typeof(PlcException))] public void T18_ReadStructThrowsIfPlcIsNotConnected() { @@ -767,7 +791,7 @@ namespace S7.Net.UnitTest tc.DWordVariable = 850; plc.WriteClass(tc, DB2); - int expectedReadBytes = Types.Class.GetClassSize(tc); + int expectedReadBytes = (int)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 diff --git a/S7.Net/PlcAsynchronous.cs b/S7.Net/PlcAsynchronous.cs index 38cb7c6..f3457ae 100644 --- a/S7.Net/PlcAsynchronous.cs +++ b/S7.Net/PlcAsynchronous.cs @@ -141,7 +141,7 @@ namespace S7.Net /// The number of read bytes public async Task> ReadClassAsync(object sourceClass, int db, int startByteAdr = 0) { - int numBytes = Class.GetClassSize(sourceClass); + int numBytes = (int)Class.GetClassSize(sourceClass); if (numBytes <= 0) { throw new Exception("The size of the class is less than 1 byte and therefore cannot be read"); diff --git a/S7.Net/PlcSynchronous.cs b/S7.Net/PlcSynchronous.cs index dd7030c..4c30636 100644 --- a/S7.Net/PlcSynchronous.cs +++ b/S7.Net/PlcSynchronous.cs @@ -162,7 +162,7 @@ namespace S7.Net /// The number of read bytes public int ReadClass(object sourceClass, int db, int startByteAdr = 0) { - int numBytes = Class.GetClassSize(sourceClass); + int numBytes = (int)Class.GetClassSize(sourceClass); if (numBytes <= 0) { throw new Exception("The size of the class is less than 1 byte and therefore cannot be read"); diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 81f2c19..c1dc14f 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -25,10 +25,8 @@ namespace S7.Net.Types } - private static double GetIncreasedNumberOfBytes(double startingNumberOfBytes, Type type) + private static double GetIncreasedNumberOfBytes(double numBytes, Type type) { - double numBytes = startingNumberOfBytes; - switch (type.Name) { case "Boolean": @@ -61,7 +59,7 @@ namespace S7.Net.Types break; default: var propertyClass = Activator.CreateInstance(type); - numBytes += GetClassSize(propertyClass); + numBytes = GetClassSize(propertyClass, numBytes, true); break; } @@ -73,10 +71,8 @@ namespace S7.Net.Types /// /// An instance of the class /// the number of bytes - public static int GetClassSize(object instance) + public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false) { - double numBytes = 0.0; - var properties = GetAccessableProperties(instance.GetType()); foreach (var property in properties) { @@ -99,11 +95,14 @@ namespace S7.Net.Types numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType); } } - // enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; - return (int)numBytes; + if (false == isInnerProperty) + { + // enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count + numBytes = Math.Ceiling(numBytes); + if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) + numBytes++; + } + return numBytes; } private static object GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes) @@ -196,14 +195,8 @@ namespace S7.Net.Types 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; - } + numBytes = FromBytes(propClass, bytes, numBytes); + value = propClass; break; } @@ -215,16 +208,10 @@ namespace S7.Net.Types /// /// The object to fill in the given array of bytes /// The array of bytes - public static void FromBytes(object sourceClass, byte[] bytes) + public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false) { if (bytes == null) - return; - - if (bytes.Length != GetClassSize(sourceClass)) - return; - - // and decode it - double numBytes = 0.0; + return numBytes; var properties = GetAccessableProperties(sourceClass.GetType()); foreach (var property in properties) @@ -248,6 +235,8 @@ namespace S7.Net.Types null); } } + + return numBytes; } private static void ToBytes(object propertyValue, byte[] bytes, ref double numBytes) @@ -317,7 +306,7 @@ namespace S7.Net.Types /// A byte array or null if fails. public static byte[] ToBytes(object sourceClass) { - int size = GetClassSize(sourceClass); + int size = (int)GetClassSize(sourceClass); byte[] bytes = new byte[size]; double numBytes = 0.0;