mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 22:38:27 +08:00
Merge pull request #191 from admo/issue190_nested_classes
Fix for Addresses are not aligned in case of custom type in class
This commit is contained in:
@@ -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)
|
||||
|
||||
51
S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs
Normal file
51
S7.Net.UnitTest/Helpers/TestClassWithNestedClass.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// DB1.DBX0.0
|
||||
/// </summary>
|
||||
public bool BitVariable00 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBX0.1
|
||||
/// </summary>
|
||||
public TestClassInnerWithBool BitVariable01 { get; set; } = new TestClassInnerWithBool();
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBB1.0
|
||||
/// </summary>
|
||||
public TestClassInnerWithByte ByteVariable02 { get; set; } = new TestClassInnerWithByte();
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBX2.0
|
||||
/// </summary>
|
||||
public bool BitVariable03 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// DB1.DBW4
|
||||
/// </summary>
|
||||
public TestClassInnerWithShort ShortVariable04 { get; set; } = new TestClassInnerWithShort();
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="ConnectionRequestTest.cs" />
|
||||
<Compile Include="ConvertersUnitTest.cs" />
|
||||
<Compile Include="Helpers\TestClassWithNestedClass.cs" />
|
||||
<Compile Include="ProtocolTests.cs" />
|
||||
<Compile Include="Helpers\ConsoleManager.cs" />
|
||||
<Compile Include="Helpers\NativeMethods.cs" />
|
||||
|
||||
@@ -160,6 +160,31 @@ namespace S7.Net.UnitTest
|
||||
Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Test_Async_ReadAndWriteNestedClass()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
TestClassWithNestedClass tc = new TestClassWithNestedClass
|
||||
{
|
||||
BitVariable00 = true,
|
||||
BitVariable01 = new TestClassInnerWithBool { BitVariable00 = true },
|
||||
ByteVariable02 = new TestClassInnerWithByte { ByteVariable00 = 128 },
|
||||
BitVariable03 = true,
|
||||
ShortVariable04 = new TestClassInnerWithShort { ShortVarialbe00 = -15000 }
|
||||
};
|
||||
|
||||
await plc.WriteClassAsync(tc, DB4);
|
||||
TestClassWithNestedClass tc2 = new TestClassWithNestedClass();
|
||||
// Values that are read from a class are stored inside the class itself, that is passed by reference
|
||||
await plc.ReadClassAsync(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read/Write a struct that has the same properties of a DB with the same field in the same order
|
||||
/// </summary>
|
||||
@@ -663,6 +688,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<TestClassWithNestedClass>(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 +784,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
|
||||
|
||||
@@ -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,54 @@ 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]
|
||||
public void T32_ReadAndWriteNestedClass()
|
||||
{
|
||||
Assert.IsTrue(plc.IsConnected, "Before executing this test, the plc must be connected. Check constructor.");
|
||||
|
||||
TestClassWithNestedClass tc = new TestClassWithNestedClass
|
||||
{
|
||||
BitVariable00 = true,
|
||||
BitVariable01 = new TestClassInnerWithBool { BitVariable00 = true },
|
||||
ByteVariable02 = new TestClassInnerWithByte { ByteVariable00 = 128 },
|
||||
BitVariable03 = true,
|
||||
ShortVariable04 = new TestClassInnerWithShort { ShortVarialbe00 = -15000 }
|
||||
};
|
||||
|
||||
plc.WriteClass(tc, DB4);
|
||||
TestClassWithNestedClass tc2 = new TestClassWithNestedClass();
|
||||
// Values that are read from a class are stored inside the class itself, that is passed by reference
|
||||
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 +816,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
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace S7.Net
|
||||
/// <returns>The number of read bytes</returns>
|
||||
public async Task<Tuple<int, object>> 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");
|
||||
@@ -370,8 +370,9 @@ namespace S7.Net
|
||||
/// <returns>A task that represents the asynchronous write operation.</returns>
|
||||
public async Task WriteClassAsync(object classValue, int db, int startByteAdr = 0)
|
||||
{
|
||||
var bytes = Types.Class.ToBytes(classValue).ToList();
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes.ToArray());
|
||||
byte[] bytes = new byte[(int)Class.GetClassSize(classValue)];
|
||||
Types.Class.ToBytes(classValue, bytes);
|
||||
await WriteBytesAsync(DataType.DataBlock, db, startByteAdr, bytes);
|
||||
}
|
||||
|
||||
private async Task<byte[]> ReadBytesWithSingleRequestAsync(DataType dataType, int db, int startByteAdr, int count)
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace S7.Net
|
||||
/// <returns>The number of read bytes</returns>
|
||||
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");
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <param name="instance">An instance of the class</param>
|
||||
/// <returns>the number of bytes</returns>
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="sourceClass">The object to fill in the given array of bytes</param>
|
||||
/// <param name="bytes">The array of bytes</param>
|
||||
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,9 +235,11 @@ namespace S7.Net.Types
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
private static void ToBytes(object propertyValue, byte[] bytes, ref double numBytes)
|
||||
private static double SetBytesFromProperty(object propertyValue, byte[] bytes, double numBytes)
|
||||
{
|
||||
int bytePos = 0;
|
||||
int bitPos = 0;
|
||||
@@ -293,7 +282,7 @@ namespace S7.Net.Types
|
||||
bytes2 = Single.ToByteArray((float)propertyValue);
|
||||
break;
|
||||
default:
|
||||
bytes2 = ToBytes(propertyValue);
|
||||
numBytes = ToBytes(propertyValue, bytes, numBytes);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -308,6 +297,8 @@ namespace S7.Net.Types
|
||||
bytes[bytePos + bCnt] = bytes2[bCnt];
|
||||
numBytes += bytes2.Length;
|
||||
}
|
||||
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -315,12 +306,8 @@ namespace S7.Net.Types
|
||||
/// </summary>
|
||||
/// <param name="sourceClass">The struct object</param>
|
||||
/// <returns>A byte array or null if fails.</returns>
|
||||
public static byte[] ToBytes(object sourceClass)
|
||||
public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0)
|
||||
{
|
||||
int size = GetClassSize(sourceClass);
|
||||
byte[] bytes = new byte[size];
|
||||
double numBytes = 0.0;
|
||||
|
||||
var properties = GetAccessableProperties(sourceClass.GetType());
|
||||
foreach (var property in properties)
|
||||
{
|
||||
@@ -330,15 +317,15 @@ namespace S7.Net.Types
|
||||
Type elementType = property.PropertyType.GetElementType();
|
||||
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
||||
{
|
||||
ToBytes(array.GetValue(i), bytes, ref numBytes);
|
||||
numBytes = SetBytesFromProperty(array.GetValue(i), bytes, numBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ToBytes(property.GetValue(sourceClass, null), bytes, ref numBytes);
|
||||
numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), bytes, numBytes);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
return numBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user