mirror of
https://github.com/S7NetPlus/s7netplus.git
synced 2026-02-17 22:38:27 +08:00
There was an Error when you had Structs conaining less than 8 Bits. The size calculation in this case returned 0 and the Plc.ReadClass() method throwed an excpetion. Structs in Step7 within da DataBlock always starts with adresses that can by devided by two. The extended code ensures the correct size even if there are a couple of structs in a DataBlock containing only a few bits.
329 lines
13 KiB
C#
329 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace S7.Net.Types
|
|
{
|
|
/// <summary>
|
|
/// Contains the methods to convert a C# class to S7 data types
|
|
/// </summary>
|
|
public static class Class
|
|
{
|
|
private static IEnumerable<PropertyInfo> GetAccessableProperties(Type classType)
|
|
{
|
|
return classType
|
|
#if NETFX_CORE
|
|
.GetProperties().Where(p => p.GetSetMethod() != null);
|
|
#else
|
|
.GetProperties(
|
|
BindingFlags.SetProperty |
|
|
BindingFlags.Public |
|
|
BindingFlags.Instance)
|
|
.Where(p => p.GetSetMethod() != null);
|
|
#endif
|
|
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the size of the class in bytes.
|
|
/// </summary>
|
|
/// <param name="instance">An instance of the class</param>
|
|
/// <returns>the number of bytes</returns>
|
|
public static int GetClassSize(object instance)
|
|
{
|
|
double numBytes = 0.0;
|
|
|
|
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);
|
|
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);
|
|
}
|
|
}
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the object's values with the given array of bytes
|
|
/// </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)
|
|
{
|
|
if (bytes == null)
|
|
return;
|
|
|
|
if (bytes.Length != GetClassSize(sourceClass))
|
|
return;
|
|
|
|
// and decode it
|
|
double numBytes = 0.0;
|
|
|
|
var properties = GetAccessableProperties(sourceClass.GetType());
|
|
foreach (var property in properties)
|
|
{
|
|
if (property.PropertyType.IsArray)
|
|
{
|
|
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(object propertyValue, byte[] bytes, ref 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 "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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a byte array depending on the struct type.
|
|
/// </summary>
|
|
/// <param name="sourceClass">The struct object</param>
|
|
/// <returns>A byte array or null if fails.</returns>
|
|
public static byte[] ToBytes(object sourceClass)
|
|
{
|
|
int size = GetClassSize(sourceClass);
|
|
byte[] bytes = new byte[size];
|
|
double numBytes = 0.0;
|
|
|
|
var properties = GetAccessableProperties(sourceClass.GetType());
|
|
foreach (var property in properties)
|
|
{
|
|
if (property.PropertyType.IsArray)
|
|
{
|
|
Array array = (Array)property.GetValue(sourceClass, null);
|
|
Type elementType = property.PropertyType.GetElementType();
|
|
for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
|
|
{
|
|
ToBytes(array.GetValue(i), bytes, ref numBytes);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ToBytes(property.GetValue(sourceClass, null), bytes, ref numBytes);
|
|
}
|
|
}
|
|
return bytes;
|
|
}
|
|
}
|
|
}
|