using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
namespace S7.Net.Types
{
///
/// Contains the methods to convert a C# class to S7 data types
///
public static class Class
{
private static IEnumerable GetAccessableProperties(Type classType)
{
return classType
#if NETSTANDARD1_3
.GetTypeInfo().DeclaredProperties.Where(p => p.SetMethod != null);
#else
.GetProperties(
BindingFlags.SetProperty |
BindingFlags.Public |
BindingFlags.Instance)
.Where(p => p.GetSetMethod() != null);
#endif
}
private static double GetIncreasedNumberOfBytes(double numBytes, Type type, PropertyInfo? propertyInfo)
{
switch (type.Name)
{
case "Boolean":
numBytes += 0.125;
break;
case "Byte":
numBytes = Math.Ceiling(numBytes);
numBytes++;
break;
case "Int16":
case "UInt16":
IncrementToEven(ref numBytes);
numBytes += 2;
break;
case "Int32":
case "UInt32":
IncrementToEven(ref numBytes);
numBytes += 4;
break;
case "Single":
IncrementToEven(ref numBytes);
numBytes += 4;
break;
case "Double":
IncrementToEven(ref numBytes);
numBytes += 8;
break;
case "String":
S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault();
if (attribute == default(S7StringAttribute))
throw new ArgumentException("Please add S7StringAttribute to the string field");
IncrementToEven(ref numBytes);
numBytes += attribute.ReservedLengthInBytes;
break;
default:
var propertyClass = Activator.CreateInstance(type) ??
throw new ArgumentException($"Failed to create instance of type {type}.", nameof(type));
numBytes = GetClassSize(propertyClass, numBytes, true);
break;
}
return numBytes;
}
///
/// Gets the size of the class in bytes.
///
/// An instance of the class
/// The offset of the current field.
/// if this property belongs to a class being serialized as member of the class requested for serialization; otherwise, .
/// the number of bytes
public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false)
{
var properties = GetAccessableProperties(instance.GetType());
foreach (var property in properties)
{
if (property.PropertyType.IsArray)
{
Type elementType = property.PropertyType.GetElementType()!;
Array array = (Array?) property.GetValue(instance, null) ??
throw new ArgumentException($"Property {property.Name} on {instance} must have a non-null value to get it's size.", nameof(instance));
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.");
}
IncrementToEven(ref numBytes);
for (int i = 0; i < array.Length; i++)
{
numBytes = GetIncreasedNumberOfBytes(numBytes, elementType, property);
}
}
else
{
numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType, property);
}
}
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, PropertyInfo? propertyInfo, 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":
IncrementToEven(ref numBytes);
// hier auswerten
ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
value = source.ConvertToShort();
numBytes += 2;
break;
case "UInt16":
IncrementToEven(ref numBytes);
// hier auswerten
value = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
numBytes += 2;
break;
case "Int32":
IncrementToEven(ref numBytes);
var wordBuffer = new byte[4];
Array.Copy(bytes, (int)numBytes, wordBuffer, 0, wordBuffer.Length);
uint sourceUInt = DWord.FromByteArray(wordBuffer);
value = sourceUInt.ConvertToInt();
numBytes += 4;
break;
case "UInt32":
IncrementToEven(ref numBytes);
var wordBuffer2 = new byte[4];
Array.Copy(bytes, (int)numBytes, wordBuffer2, 0, wordBuffer2.Length);
value = DWord.FromByteArray(wordBuffer2);
numBytes += 4;
break;
case "Single":
IncrementToEven(ref numBytes);
// hier auswerten
value = Real.FromByteArray(
new byte[] {
bytes[(int)numBytes],
bytes[(int)numBytes + 1],
bytes[(int)numBytes + 2],
bytes[(int)numBytes + 3] });
numBytes += 4;
break;
case "Double":
IncrementToEven(ref numBytes);
var buffer = new byte[8];
Array.Copy(bytes, (int)numBytes, buffer, 0, 8);
// hier auswerten
value = LReal.FromByteArray(buffer);
numBytes += 8;
break;
case "String":
S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault();
if (attribute == default(S7StringAttribute))
throw new ArgumentException("Please add S7StringAttribute to the string field");
IncrementToEven(ref numBytes);
// get the value
var sData = new byte[attribute.ReservedLengthInBytes];
Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length);
value = attribute.Type switch
{
S7StringType.S7String => S7String.FromByteArray(sData),
S7StringType.S7WString => S7WString.FromByteArray(sData),
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
};
numBytes += sData.Length;
break;
default:
var propClass = Activator.CreateInstance(propertyType) ??
throw new ArgumentException($"Failed to create instance of type {propertyType}.", nameof(propertyType));
numBytes = FromBytes(propClass, bytes, numBytes);
value = propClass;
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 array of bytes
/// The offset for the current field.
/// if this class is the type of a member of the class to be serialized; otherwise, .
public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false)
{
if (bytes == null)
return numBytes;
var properties = GetAccessableProperties(sourceClass.GetType());
foreach (var property in properties)
{
if (property.PropertyType.IsArray)
{
Array array = (Array?) property.GetValue(sourceClass, null) ??
throw new ArgumentException($"Property {property.Name} on sourceClass must be an array instance.", nameof(sourceClass));
IncrementToEven(ref numBytes);
Type elementType = property.PropertyType.GetElementType()!;
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
{
array.SetValue(
GetPropertyValue(elementType, property, bytes, ref numBytes),
i);
}
}
else
{
property.SetValue(
sourceClass,
GetPropertyValue(property.PropertyType, property, bytes, ref numBytes),
null);
}
}
return numBytes;
}
private static double SetBytesFromProperty(object propertyValue, PropertyInfo? propertyInfo, byte[] bytes, double numBytes)
{
int bytePos = 0;
int bitPos = 0;
byte[]? bytes2 = null;
switch (propertyValue.GetType().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 "Single":
bytes2 = Real.ToByteArray((float)propertyValue);
break;
case "Double":
bytes2 = LReal.ToByteArray((double)propertyValue);
break;
case "String":
S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault();
if (attribute == default(S7StringAttribute))
throw new ArgumentException("Please add S7StringAttribute to the string field");
bytes2 = attribute.Type switch
{
S7StringType.S7String => S7String.ToByteArray((string)propertyValue, attribute.ReservedLength),
S7StringType.S7WString => S7WString.ToByteArray((string)propertyValue, attribute.ReservedLength),
_ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
};
break;
default:
numBytes = ToBytes(propertyValue, bytes, numBytes);
break;
}
if (bytes2 != null)
{
IncrementToEven(ref numBytes);
bytePos = (int)numBytes;
for (int bCnt = 0; bCnt < bytes2.Length; bCnt++)
bytes[bytePos + bCnt] = bytes2[bCnt];
numBytes += bytes2.Length;
}
return numBytes;
}
///
/// Creates a byte array depending on the struct type.
///
/// The struct object.
/// The target byte array.
/// The offset for the current field.
/// A byte array or null if fails.
public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0)
{
var properties = GetAccessableProperties(sourceClass.GetType());
foreach (var property in properties)
{
var value = property.GetValue(sourceClass, null) ??
throw new ArgumentException($"Property {property.Name} on sourceClass can't be null.", nameof(sourceClass));
if (property.PropertyType.IsArray)
{
Array array = (Array) value;
IncrementToEven(ref numBytes);
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
{
numBytes = SetBytesFromProperty(array.GetValue(i)!, property, bytes, numBytes);
}
}
else
{
numBytes = SetBytesFromProperty(value, property, bytes, numBytes);
}
}
return numBytes;
}
private static void IncrementToEven(ref double numBytes)
{
numBytes = Math.Ceiling(numBytes);
if (numBytes % 2 > 0) numBytes++;
}
}
}