Class.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using System.Runtime.InteropServices;
  6. namespace S7.Net.Types
  7. {
  8. /// <summary>
  9. /// Contains the methods to convert a C# class to S7 data types
  10. /// </summary>
  11. public static class Class
  12. {
  13. private static IEnumerable<PropertyInfo> GetAccessableProperties(Type classType)
  14. {
  15. return classType
  16. #if NETSTANDARD1_3
  17. .GetTypeInfo().DeclaredProperties.Where(p => p.SetMethod != null);
  18. #else
  19. .GetProperties(
  20. BindingFlags.SetProperty |
  21. BindingFlags.Public |
  22. BindingFlags.Instance)
  23. .Where(p => p.GetSetMethod() != null);
  24. #endif
  25. }
  26. private static double GetIncreasedNumberOfBytes(double numBytes, Type type, PropertyInfo? propertyInfo)
  27. {
  28. switch (type.Name)
  29. {
  30. case "Boolean":
  31. numBytes += 0.125;
  32. break;
  33. case "Byte":
  34. numBytes = Math.Ceiling(numBytes);
  35. numBytes++;
  36. break;
  37. case "Int16":
  38. case "UInt16":
  39. IncrementToEven(ref numBytes);
  40. numBytes += 2;
  41. break;
  42. case "Int32":
  43. case "UInt32":
  44. IncrementToEven(ref numBytes);
  45. numBytes += 4;
  46. break;
  47. case "Single":
  48. IncrementToEven(ref numBytes);
  49. numBytes += 4;
  50. break;
  51. case "Double":
  52. IncrementToEven(ref numBytes);
  53. numBytes += 8;
  54. break;
  55. case "String":
  56. S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
  57. if (attribute == default(S7StringAttribute))
  58. throw new ArgumentException("Please add S7StringAttribute to the string field");
  59. IncrementToEven(ref numBytes);
  60. numBytes += attribute.ReservedLengthInBytes;
  61. break;
  62. default:
  63. var propertyClass = Activator.CreateInstance(type) ??
  64. throw new ArgumentException($"Failed to create instance of type {type}.", nameof(type));
  65. numBytes = GetClassSize(propertyClass, numBytes, true);
  66. break;
  67. }
  68. return numBytes;
  69. }
  70. /// <summary>
  71. /// Gets the size of the class in bytes.
  72. /// </summary>
  73. /// <param name="instance">An instance of the class</param>
  74. /// <param name="numBytes">The offset of the current field.</param>
  75. /// <param name="isInnerProperty"><see langword="true" /> if this property belongs to a class being serialized as member of the class requested for serialization; otherwise, <see langword="false" />.</param>
  76. /// <returns>the number of bytes</returns>
  77. public static double GetClassSize(object instance, double numBytes = 0.0, bool isInnerProperty = false)
  78. {
  79. var properties = GetAccessableProperties(instance.GetType());
  80. foreach (var property in properties)
  81. {
  82. if (property.PropertyType.IsArray)
  83. {
  84. Type elementType = property.PropertyType.GetElementType()!;
  85. Array array = (Array?) property.GetValue(instance, null) ??
  86. throw new ArgumentException($"Property {property.Name} on {instance} must have a non-null value to get it's size.", nameof(instance));
  87. if (array.Length <= 0)
  88. {
  89. throw new Exception("Cannot determine size of class, because an array is defined which has no fixed size greater than zero.");
  90. }
  91. IncrementToEven(ref numBytes);
  92. for (int i = 0; i < array.Length; i++)
  93. {
  94. numBytes = GetIncreasedNumberOfBytes(numBytes, elementType, property);
  95. }
  96. }
  97. else
  98. {
  99. numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType, property);
  100. }
  101. }
  102. if (false == isInnerProperty)
  103. {
  104. // enlarge numBytes to next even number because S7-Structs in a DB always will be resized to an even byte count
  105. numBytes = Math.Ceiling(numBytes);
  106. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  107. numBytes++;
  108. }
  109. return numBytes;
  110. }
  111. private static object? GetPropertyValue(Type propertyType, PropertyInfo? propertyInfo, byte[] bytes, ref double numBytes)
  112. {
  113. object? value = null;
  114. switch (propertyType.Name)
  115. {
  116. case "Boolean":
  117. // get the value
  118. int bytePos = (int)Math.Floor(numBytes);
  119. int bitPos = (int)((numBytes - (double)bytePos) / 0.125);
  120. if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0)
  121. value = true;
  122. else
  123. value = false;
  124. numBytes += 0.125;
  125. break;
  126. case "Byte":
  127. numBytes = Math.Ceiling(numBytes);
  128. value = (byte)(bytes[(int)numBytes]);
  129. numBytes++;
  130. break;
  131. case "Int16":
  132. IncrementToEven(ref numBytes);
  133. // hier auswerten
  134. ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
  135. value = source.ConvertToShort();
  136. numBytes += 2;
  137. break;
  138. case "UInt16":
  139. IncrementToEven(ref numBytes);
  140. // hier auswerten
  141. value = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
  142. numBytes += 2;
  143. break;
  144. case "Int32":
  145. IncrementToEven(ref numBytes);
  146. var wordBuffer = new byte[4];
  147. Array.Copy(bytes, (int)numBytes, wordBuffer, 0, wordBuffer.Length);
  148. uint sourceUInt = DWord.FromByteArray(wordBuffer);
  149. value = sourceUInt.ConvertToInt();
  150. numBytes += 4;
  151. break;
  152. case "UInt32":
  153. IncrementToEven(ref numBytes);
  154. var wordBuffer2 = new byte[4];
  155. Array.Copy(bytes, (int)numBytes, wordBuffer2, 0, wordBuffer2.Length);
  156. value = DWord.FromByteArray(wordBuffer2);
  157. numBytes += 4;
  158. break;
  159. case "Single":
  160. IncrementToEven(ref numBytes);
  161. // hier auswerten
  162. value = Real.FromByteArray(
  163. new byte[] {
  164. bytes[(int)numBytes],
  165. bytes[(int)numBytes + 1],
  166. bytes[(int)numBytes + 2],
  167. bytes[(int)numBytes + 3] });
  168. numBytes += 4;
  169. break;
  170. case "Double":
  171. IncrementToEven(ref numBytes);
  172. var buffer = new byte[8];
  173. Array.Copy(bytes, (int)numBytes, buffer, 0, 8);
  174. // hier auswerten
  175. value = LReal.FromByteArray(buffer);
  176. numBytes += 8;
  177. break;
  178. case "String":
  179. S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
  180. if (attribute == default(S7StringAttribute))
  181. throw new ArgumentException("Please add S7StringAttribute to the string field");
  182. IncrementToEven(ref numBytes);
  183. // get the value
  184. var sData = new byte[attribute.ReservedLengthInBytes];
  185. Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length);
  186. value = attribute.Type switch
  187. {
  188. S7StringType.S7String => S7String.FromByteArray(sData),
  189. S7StringType.S7WString => S7WString.FromByteArray(sData),
  190. _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
  191. };
  192. numBytes += sData.Length;
  193. break;
  194. default:
  195. var propClass = Activator.CreateInstance(propertyType) ??
  196. throw new ArgumentException($"Failed to create instance of type {propertyType}.", nameof(propertyType));
  197. numBytes = FromBytes(propClass, bytes, numBytes);
  198. value = propClass;
  199. break;
  200. }
  201. return value;
  202. }
  203. /// <summary>
  204. /// Sets the object's values with the given array of bytes
  205. /// </summary>
  206. /// <param name="sourceClass">The object to fill in the given array of bytes</param>
  207. /// <param name="bytes">The array of bytes</param>
  208. /// <param name="numBytes">The offset for the current field.</param>
  209. /// <param name="isInnerClass"><see langword="true" /> if this class is the type of a member of the class to be serialized; otherwise, <see langword="false" />.</param>
  210. public static double FromBytes(object sourceClass, byte[] bytes, double numBytes = 0, bool isInnerClass = false)
  211. {
  212. if (bytes == null)
  213. return numBytes;
  214. var properties = GetAccessableProperties(sourceClass.GetType());
  215. foreach (var property in properties)
  216. {
  217. if (property.PropertyType.IsArray)
  218. {
  219. Array array = (Array?) property.GetValue(sourceClass, null) ??
  220. throw new ArgumentException($"Property {property.Name} on sourceClass must be an array instance.", nameof(sourceClass));
  221. IncrementToEven(ref numBytes);
  222. Type elementType = property.PropertyType.GetElementType()!;
  223. for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
  224. {
  225. array.SetValue(
  226. GetPropertyValue(elementType, property, bytes, ref numBytes),
  227. i);
  228. }
  229. }
  230. else
  231. {
  232. property.SetValue(
  233. sourceClass,
  234. GetPropertyValue(property.PropertyType, property, bytes, ref numBytes),
  235. null);
  236. }
  237. }
  238. return numBytes;
  239. }
  240. private static double SetBytesFromProperty(object propertyValue, PropertyInfo? propertyInfo, byte[] bytes, double numBytes)
  241. {
  242. int bytePos = 0;
  243. int bitPos = 0;
  244. byte[]? bytes2 = null;
  245. switch (propertyValue.GetType().Name)
  246. {
  247. case "Boolean":
  248. // get the value
  249. bytePos = (int)Math.Floor(numBytes);
  250. bitPos = (int)((numBytes - (double)bytePos) / 0.125);
  251. if ((bool)propertyValue)
  252. bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true
  253. else
  254. bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false
  255. numBytes += 0.125;
  256. break;
  257. case "Byte":
  258. numBytes = (int)Math.Ceiling(numBytes);
  259. bytePos = (int)numBytes;
  260. bytes[bytePos] = (byte)propertyValue;
  261. numBytes++;
  262. break;
  263. case "Int16":
  264. bytes2 = Int.ToByteArray((Int16)propertyValue);
  265. break;
  266. case "UInt16":
  267. bytes2 = Word.ToByteArray((UInt16)propertyValue);
  268. break;
  269. case "Int32":
  270. bytes2 = DInt.ToByteArray((Int32)propertyValue);
  271. break;
  272. case "UInt32":
  273. bytes2 = DWord.ToByteArray((UInt32)propertyValue);
  274. break;
  275. case "Single":
  276. bytes2 = Real.ToByteArray((float)propertyValue);
  277. break;
  278. case "Double":
  279. bytes2 = LReal.ToByteArray((double)propertyValue);
  280. break;
  281. case "String":
  282. S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
  283. if (attribute == default(S7StringAttribute))
  284. throw new ArgumentException("Please add S7StringAttribute to the string field");
  285. bytes2 = attribute.Type switch
  286. {
  287. S7StringType.S7String => S7String.ToByteArray((string)propertyValue, attribute.ReservedLength),
  288. S7StringType.S7WString => S7WString.ToByteArray((string)propertyValue, attribute.ReservedLength),
  289. _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
  290. };
  291. break;
  292. default:
  293. numBytes = ToBytes(propertyValue, bytes, numBytes);
  294. break;
  295. }
  296. if (bytes2 != null)
  297. {
  298. IncrementToEven(ref numBytes);
  299. bytePos = (int)numBytes;
  300. for (int bCnt = 0; bCnt < bytes2.Length; bCnt++)
  301. bytes[bytePos + bCnt] = bytes2[bCnt];
  302. numBytes += bytes2.Length;
  303. }
  304. return numBytes;
  305. }
  306. /// <summary>
  307. /// Creates a byte array depending on the struct type.
  308. /// </summary>
  309. /// <param name="sourceClass">The struct object.</param>
  310. /// <param name="bytes">The target byte array.</param>
  311. /// <param name="numBytes">The offset for the current field.</param>
  312. /// <returns>A byte array or null if fails.</returns>
  313. public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = 0.0)
  314. {
  315. var properties = GetAccessableProperties(sourceClass.GetType());
  316. foreach (var property in properties)
  317. {
  318. var value = property.GetValue(sourceClass, null) ??
  319. throw new ArgumentException($"Property {property.Name} on sourceClass can't be null.", nameof(sourceClass));
  320. if (property.PropertyType.IsArray)
  321. {
  322. Array array = (Array) value;
  323. IncrementToEven(ref numBytes);
  324. for (int i = 0; i < array.Length && numBytes < bytes.Length; i++)
  325. {
  326. numBytes = SetBytesFromProperty(array.GetValue(i)!, property, bytes, numBytes);
  327. }
  328. }
  329. else
  330. {
  331. numBytes = SetBytesFromProperty(value, property, bytes, numBytes);
  332. }
  333. }
  334. return numBytes;
  335. }
  336. private static void IncrementToEven(ref double numBytes)
  337. {
  338. numBytes = Math.Ceiling(numBytes);
  339. if (numBytes % 2 > 0) numBytes++;
  340. }
  341. }
  342. }