123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- using System;
- using System.Linq;
- 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":
- case "TimeSpan":
- 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;
- case "String":
- S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
- if (attribute == default(S7StringAttribute))
- throw new ArgumentException("Please add S7StringAttribute to the string field");
- numBytes = Math.Ceiling(numBytes);
- if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
- numBytes++;
- numBytes += attribute.ReservedLengthInBytes;
- 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) ??
- throw new ArgumentException($"Failed to create an instance of the type {structType}.", nameof(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++;
- // get the value
- 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++;
- // get the value
- 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++;
- // get the value
- 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++;
- // get the value
- 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++;
- // get the value
- 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++;
- // get the value
- var data = new byte[8];
- Array.Copy(bytes, (int)numBytes, data, 0, 8);
- info.SetValue(structValue, LReal.FromByteArray(data));
- numBytes += 8;
- break;
- case "String":
- S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
- if (attribute == default(S7StringAttribute))
- throw new ArgumentException("Please add S7StringAttribute to the string field");
- numBytes = Math.Ceiling(numBytes);
- if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
- numBytes++;
- // get the value
- var sData = new byte[attribute.ReservedLengthInBytes];
- Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length);
- switch (attribute.Type)
- {
- case S7StringType.S7String:
- info.SetValue(structValue, S7String.FromByteArray(sData));
- break;
- case S7StringType.S7WString:
- info.SetValue(structValue, S7WString.FromByteArray(sData));
- break;
- default:
- throw new ArgumentException("Please use a valid string type for the S7StringAttribute");
- }
- numBytes += sData.Length;
- break;
- case "TimeSpan":
- numBytes = Math.Ceiling(numBytes);
- if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
- numBytes++;
-
- // get the value
- info.SetValue(structValue, TimeSpan.FromByteArray(new[]
- {
- bytes[(int)numBytes + 0],
- bytes[(int)numBytes + 1],
- bytes[(int)numBytes + 2],
- bytes[(int)numBytes + 3]
- }));
- numBytes += 4;
- 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)
- {
- static TValue GetValueOrThrow<TValue>(FieldInfo fi, object structValue) where TValue : struct
- {
- var value = fi.GetValue(structValue) as TValue? ??
- throw new ArgumentException($"Failed to convert value of field {fi.Name} of {structValue} to type {typeof(TValue)}");
- return value;
- }
- bytes2 = null;
- switch (info.FieldType.Name)
- {
- case "Boolean":
- // get the value
- bytePos = (int)Math.Floor(numBytes);
- bitPos = (int)((numBytes - (double)bytePos) / 0.125);
- if (GetValueOrThrow<bool>(info, 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] = GetValueOrThrow<byte>(info, structValue);
- numBytes++;
- break;
- case "Int16":
- bytes2 = Int.ToByteArray(GetValueOrThrow<short>(info, structValue));
- break;
- case "UInt16":
- bytes2 = Word.ToByteArray(GetValueOrThrow<ushort>(info, structValue));
- break;
- case "Int32":
- bytes2 = DInt.ToByteArray(GetValueOrThrow<int>(info, structValue));
- break;
- case "UInt32":
- bytes2 = DWord.ToByteArray(GetValueOrThrow<uint>(info, structValue));
- break;
- case "Single":
- bytes2 = Real.ToByteArray(GetValueOrThrow<float>(info, structValue));
- break;
- case "Double":
- bytes2 = LReal.ToByteArray(GetValueOrThrow<double>(info, structValue));
- break;
- case "String":
- S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
- if (attribute == default(S7StringAttribute))
- throw new ArgumentException("Please add S7StringAttribute to the string field");
- bytes2 = attribute.Type switch
- {
- S7StringType.S7String => S7String.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength),
- S7StringType.S7WString => S7WString.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength),
- _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
- };
- break;
- case "TimeSpan":
- bytes2 = TimeSpan.ToByteArray((System.TimeSpan)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;
- }
- }
- }
|