Struct.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. using System;
  2. using System.Linq;
  3. using System.Reflection;
  4. namespace S7.Net.Types
  5. {
  6. /// <summary>
  7. /// Contains the method to convert a C# struct to S7 data types
  8. /// </summary>
  9. public static class Struct
  10. {
  11. /// <summary>
  12. /// Gets the size of the struct in bytes.
  13. /// </summary>
  14. /// <param name="structType">the type of the struct</param>
  15. /// <returns>the number of bytes</returns>
  16. public static int GetStructSize(Type structType)
  17. {
  18. double numBytes = 0.0;
  19. var infos = structType
  20. #if NETSTANDARD1_3
  21. .GetTypeInfo().DeclaredFields;
  22. #else
  23. .GetFields();
  24. #endif
  25. foreach (var info in infos)
  26. {
  27. switch (info.FieldType.Name)
  28. {
  29. case "Boolean":
  30. numBytes += 0.125;
  31. break;
  32. case "Byte":
  33. numBytes = Math.Ceiling(numBytes);
  34. numBytes++;
  35. break;
  36. case "Int16":
  37. case "UInt16":
  38. numBytes = Math.Ceiling(numBytes);
  39. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  40. numBytes++;
  41. numBytes += 2;
  42. break;
  43. case "Int32":
  44. case "UInt32":
  45. case "TimeSpan":
  46. numBytes = Math.Ceiling(numBytes);
  47. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  48. numBytes++;
  49. numBytes += 4;
  50. break;
  51. case "Single":
  52. numBytes = Math.Ceiling(numBytes);
  53. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  54. numBytes++;
  55. numBytes += 4;
  56. break;
  57. case "Double":
  58. numBytes = Math.Ceiling(numBytes);
  59. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  60. numBytes++;
  61. numBytes += 8;
  62. break;
  63. case "String":
  64. S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
  65. if (attribute == default(S7StringAttribute))
  66. throw new ArgumentException("Please add S7StringAttribute to the string field");
  67. numBytes = Math.Ceiling(numBytes);
  68. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  69. numBytes++;
  70. numBytes += attribute.ReservedLengthInBytes;
  71. break;
  72. default:
  73. numBytes += GetStructSize(info.FieldType);
  74. break;
  75. }
  76. }
  77. return (int)numBytes;
  78. }
  79. /// <summary>
  80. /// Creates a struct of a specified type by an array of bytes.
  81. /// </summary>
  82. /// <param name="structType">The struct type</param>
  83. /// <param name="bytes">The array of bytes</param>
  84. /// <returns>The object depending on the struct type or null if fails(array-length != struct-length</returns>
  85. public static object? FromBytes(Type structType, byte[] bytes)
  86. {
  87. if (bytes == null)
  88. return null;
  89. if (bytes.Length != GetStructSize(structType))
  90. return null;
  91. // and decode it
  92. int bytePos = 0;
  93. int bitPos = 0;
  94. double numBytes = 0.0;
  95. object structValue = Activator.CreateInstance(structType) ??
  96. throw new ArgumentException($"Failed to create an instance of the type {structType}.", nameof(structType));
  97. var infos = structValue.GetType()
  98. #if NETSTANDARD1_3
  99. .GetTypeInfo().DeclaredFields;
  100. #else
  101. .GetFields();
  102. #endif
  103. foreach (var info in infos)
  104. {
  105. switch (info.FieldType.Name)
  106. {
  107. case "Boolean":
  108. // get the value
  109. bytePos = (int)Math.Floor(numBytes);
  110. bitPos = (int)((numBytes - (double)bytePos) / 0.125);
  111. if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0)
  112. info.SetValue(structValue, true);
  113. else
  114. info.SetValue(structValue, false);
  115. numBytes += 0.125;
  116. break;
  117. case "Byte":
  118. numBytes = Math.Ceiling(numBytes);
  119. info.SetValue(structValue, (byte)(bytes[(int)numBytes]));
  120. numBytes++;
  121. break;
  122. case "Int16":
  123. numBytes = Math.Ceiling(numBytes);
  124. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  125. numBytes++;
  126. // get the value
  127. ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]);
  128. info.SetValue(structValue, source.ConvertToShort());
  129. numBytes += 2;
  130. break;
  131. case "UInt16":
  132. numBytes = Math.Ceiling(numBytes);
  133. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  134. numBytes++;
  135. // get the value
  136. info.SetValue(structValue, Word.FromBytes(bytes[(int)numBytes + 1],
  137. bytes[(int)numBytes]));
  138. numBytes += 2;
  139. break;
  140. case "Int32":
  141. numBytes = Math.Ceiling(numBytes);
  142. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  143. numBytes++;
  144. // get the value
  145. uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3],
  146. bytes[(int)numBytes + 2],
  147. bytes[(int)numBytes + 1],
  148. bytes[(int)numBytes + 0]);
  149. info.SetValue(structValue, sourceUInt.ConvertToInt());
  150. numBytes += 4;
  151. break;
  152. case "UInt32":
  153. numBytes = Math.Ceiling(numBytes);
  154. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  155. numBytes++;
  156. // get the value
  157. info.SetValue(structValue, DWord.FromBytes(bytes[(int)numBytes],
  158. bytes[(int)numBytes + 1],
  159. bytes[(int)numBytes + 2],
  160. bytes[(int)numBytes + 3]));
  161. numBytes += 4;
  162. break;
  163. case "Single":
  164. numBytes = Math.Ceiling(numBytes);
  165. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  166. numBytes++;
  167. // get the value
  168. info.SetValue(structValue, Real.FromByteArray(new byte[] { bytes[(int)numBytes],
  169. bytes[(int)numBytes + 1],
  170. bytes[(int)numBytes + 2],
  171. bytes[(int)numBytes + 3] }));
  172. numBytes += 4;
  173. break;
  174. case "Double":
  175. numBytes = Math.Ceiling(numBytes);
  176. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  177. numBytes++;
  178. // get the value
  179. var data = new byte[8];
  180. Array.Copy(bytes, (int)numBytes, data, 0, 8);
  181. info.SetValue(structValue, LReal.FromByteArray(data));
  182. numBytes += 8;
  183. break;
  184. case "String":
  185. S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
  186. if (attribute == default(S7StringAttribute))
  187. throw new ArgumentException("Please add S7StringAttribute to the string field");
  188. numBytes = Math.Ceiling(numBytes);
  189. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  190. numBytes++;
  191. // get the value
  192. var sData = new byte[attribute.ReservedLengthInBytes];
  193. Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length);
  194. switch (attribute.Type)
  195. {
  196. case S7StringType.S7String:
  197. info.SetValue(structValue, S7String.FromByteArray(sData));
  198. break;
  199. case S7StringType.S7WString:
  200. info.SetValue(structValue, S7WString.FromByteArray(sData));
  201. break;
  202. default:
  203. throw new ArgumentException("Please use a valid string type for the S7StringAttribute");
  204. }
  205. numBytes += sData.Length;
  206. break;
  207. case "TimeSpan":
  208. numBytes = Math.Ceiling(numBytes);
  209. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  210. numBytes++;
  211. // get the value
  212. info.SetValue(structValue, TimeSpan.FromByteArray(new[]
  213. {
  214. bytes[(int)numBytes + 0],
  215. bytes[(int)numBytes + 1],
  216. bytes[(int)numBytes + 2],
  217. bytes[(int)numBytes + 3]
  218. }));
  219. numBytes += 4;
  220. break;
  221. default:
  222. var buffer = new byte[GetStructSize(info.FieldType)];
  223. if (buffer.Length == 0)
  224. continue;
  225. Buffer.BlockCopy(bytes, (int)Math.Ceiling(numBytes), buffer, 0, buffer.Length);
  226. info.SetValue(structValue, FromBytes(info.FieldType, buffer));
  227. numBytes += buffer.Length;
  228. break;
  229. }
  230. }
  231. return structValue;
  232. }
  233. /// <summary>
  234. /// Creates a byte array depending on the struct type.
  235. /// </summary>
  236. /// <param name="structValue">The struct object</param>
  237. /// <returns>A byte array or null if fails.</returns>
  238. public static byte[] ToBytes(object structValue)
  239. {
  240. Type type = structValue.GetType();
  241. int size = Struct.GetStructSize(type);
  242. byte[] bytes = new byte[size];
  243. byte[]? bytes2 = null;
  244. int bytePos = 0;
  245. int bitPos = 0;
  246. double numBytes = 0.0;
  247. var infos = type
  248. #if NETSTANDARD1_3
  249. .GetTypeInfo().DeclaredFields;
  250. #else
  251. .GetFields();
  252. #endif
  253. foreach (var info in infos)
  254. {
  255. static TValue GetValueOrThrow<TValue>(FieldInfo fi, object structValue) where TValue : struct
  256. {
  257. var value = fi.GetValue(structValue) as TValue? ??
  258. throw new ArgumentException($"Failed to convert value of field {fi.Name} of {structValue} to type {typeof(TValue)}");
  259. return value;
  260. }
  261. bytes2 = null;
  262. switch (info.FieldType.Name)
  263. {
  264. case "Boolean":
  265. // get the value
  266. bytePos = (int)Math.Floor(numBytes);
  267. bitPos = (int)((numBytes - (double)bytePos) / 0.125);
  268. if (GetValueOrThrow<bool>(info, structValue))
  269. bytes[bytePos] |= (byte)Math.Pow(2, bitPos); // is true
  270. else
  271. bytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos)); // is false
  272. numBytes += 0.125;
  273. break;
  274. case "Byte":
  275. numBytes = (int)Math.Ceiling(numBytes);
  276. bytePos = (int)numBytes;
  277. bytes[bytePos] = GetValueOrThrow<byte>(info, structValue);
  278. numBytes++;
  279. break;
  280. case "Int16":
  281. bytes2 = Int.ToByteArray(GetValueOrThrow<short>(info, structValue));
  282. break;
  283. case "UInt16":
  284. bytes2 = Word.ToByteArray(GetValueOrThrow<ushort>(info, structValue));
  285. break;
  286. case "Int32":
  287. bytes2 = DInt.ToByteArray(GetValueOrThrow<int>(info, structValue));
  288. break;
  289. case "UInt32":
  290. bytes2 = DWord.ToByteArray(GetValueOrThrow<uint>(info, structValue));
  291. break;
  292. case "Single":
  293. bytes2 = Real.ToByteArray(GetValueOrThrow<float>(info, structValue));
  294. break;
  295. case "Double":
  296. bytes2 = LReal.ToByteArray(GetValueOrThrow<double>(info, structValue));
  297. break;
  298. case "String":
  299. S7StringAttribute? attribute = info.GetCustomAttributes<S7StringAttribute>().SingleOrDefault();
  300. if (attribute == default(S7StringAttribute))
  301. throw new ArgumentException("Please add S7StringAttribute to the string field");
  302. bytes2 = attribute.Type switch
  303. {
  304. S7StringType.S7String => S7String.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength),
  305. S7StringType.S7WString => S7WString.ToByteArray((string?)info.GetValue(structValue), attribute.ReservedLength),
  306. _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute")
  307. };
  308. break;
  309. case "TimeSpan":
  310. bytes2 = TimeSpan.ToByteArray((System.TimeSpan)info.GetValue(structValue));
  311. break;
  312. }
  313. if (bytes2 != null)
  314. {
  315. // add them
  316. numBytes = Math.Ceiling(numBytes);
  317. if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)
  318. numBytes++;
  319. bytePos = (int)numBytes;
  320. for (int bCnt = 0; bCnt < bytes2.Length; bCnt++)
  321. bytes[bytePos + bCnt] = bytes2[bCnt];
  322. numBytes += bytes2.Length;
  323. }
  324. }
  325. return bytes;
  326. }
  327. }
  328. }