Files
s7netplus/S7.Net/Types/Struct.cs
Serge Camille e8a9983367 Add LReal support, kill old "Double" sham.
- Adds true support for 64bit double / LReal datatype.
- Set old Types.Single and Types.Double to obselete. Both class names use .NET types instead of S7 type names, contrary to all other types.
- Remove already obsoleted conversion from DWord to Real. Why is this even necessary?
  For users caring about converting from DWord, they can still convert to single. But unless we get LWord support, there won't be a direct conversion to double/LReal.
- Adjust unit tests by removing rounding, testing directly double read/writes.

There is quite a bit of breaking changes at least in the automated Struct and object functions which automatically translate .NET types to appropriate S7 types.
My consideration was that if we ever want to support 64bit types, there is no way against breaking those existing incorrect conversions from 64bit .NET double to 32 bit S7 Real variables.
2020-09-04 20:46:16 +02:00

276 lines
12 KiB
C#

using System;
using System.Reflection;
namespace S7.Net.Types
{
/// <summary>
/// Contains the method to convert a C# struct to S7 data types
/// </summary>
public static class Struct
{
/// <summary>
/// Gets the size of the struct in bytes.
/// </summary>
/// <param name="structType">the type of the struct</param>
/// <returns>the number of bytes</returns>
public static int GetStructSize(Type structType)
{
double numBytes = 0.0;
var infos = structType
#if NETSTANDARD1_3
.GetTypeInfo().DeclaredFields;
#else
.GetFields();
#endif
foreach (var info in infos)
{
switch (info.FieldType.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 "Single":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += 4;
break;
case "Double":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
numBytes += 8;
break;
default:
numBytes += GetStructSize(info.FieldType);
break;
}
}
return (int)numBytes;
}
/// <summary>
/// Creates a struct of a specified type by an array of bytes.
/// </summary>
/// <param name="structType">The struct type</param>
/// <param name="bytes">The array of bytes</param>
/// <returns>The object depending on the struct type or null if fails(array-length != struct-length</returns>
public static object? FromBytes(Type structType, byte[] bytes)
{
if (bytes == null)
return null;
if (bytes.Length != GetStructSize(structType))
return null;
// and decode it
int bytePos = 0;
int bitPos = 0;
double numBytes = 0.0;
object structValue = Activator.CreateInstance(structType);
var infos = structValue.GetType()
#if NETSTANDARD1_3
.GetTypeInfo().DeclaredFields;
#else
.GetFields();
#endif
foreach (var info in infos)
{
switch (info.FieldType.Name)
{
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)
info.SetValue(structValue, true);
else
info.SetValue(structValue, false);
numBytes += 0.125;
break;
case "Byte":
numBytes = Math.Ceiling(numBytes);
info.SetValue(structValue, (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]);
info.SetValue(structValue, source.ConvertToShort());
numBytes += 2;
break;
case "UInt16":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
info.SetValue(structValue, 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]);
info.SetValue(structValue, sourceUInt.ConvertToInt());
numBytes += 4;
break;
case "UInt32":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
info.SetValue(structValue, DWord.FromBytes(bytes[(int)numBytes],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 3]));
numBytes += 4;
break;
case "Single":
numBytes = Math.Ceiling(numBytes);
if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
numBytes++;
// hier auswerten
info.SetValue(structValue, Real.FromByteArray(new byte[] { 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
var data = new byte[8];
Array.Copy(bytes, (int)numBytes, data, 0, 8);
info.SetValue(structValue, LReal.FromByteArray(data));
numBytes += 8;
break;
default:
var buffer = new byte[GetStructSize(info.FieldType)];
if (buffer.Length == 0)
continue;
Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length);
info.SetValue(structValue, FromBytes(info.FieldType, buffer));
numBytes += buffer.Length;
break;
}
}
return structValue;
}
/// <summary>
/// Creates a byte array depending on the struct type.
/// </summary>
/// <param name="structValue">The struct object</param>
/// <returns>A byte array or null if fails.</returns>
public static byte[] ToBytes(object structValue)
{
Type type = structValue.GetType();
int size = Struct.GetStructSize(type);
byte[] bytes = new byte[size];
byte[]? bytes2 = null;
int bytePos = 0;
int bitPos = 0;
double numBytes = 0.0;
var infos = type
#if NETSTANDARD1_3
.GetTypeInfo().DeclaredFields;
#else
.GetFields();
#endif
foreach (var info in infos)
{
bytes2 = null;
switch (info.FieldType.Name)
{
case "Boolean":
// get the value
bytePos = (int)Math.Floor(numBytes);
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
if ((bool)info.GetValue(structValue))
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)info.GetValue(structValue);
numBytes++;
break;
case "Int16":
bytes2 = Int.ToByteArray((Int16)info.GetValue(structValue));
break;
case "UInt16":
bytes2 = Word.ToByteArray((UInt16)info.GetValue(structValue));
break;
case "Int32":
bytes2 = DInt.ToByteArray((Int32)info.GetValue(structValue));
break;
case "UInt32":
bytes2 = DWord.ToByteArray((UInt32)info.GetValue(structValue));
break;
case "Single":
bytes2 = Real.ToByteArray((float)info.GetValue(structValue));
break;
case "Double":
bytes2 = LReal.ToByteArray((double)info.GetValue(structValue));
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;
}
}
return bytes;
}
}
}