与下位机或设备的通信解析优化的一点功能:T4+动态编译

    去年接触的一个项目中,需要通过TCP与设备进行对接的,传的是Modbus协议的数据,然后后台需要可以动态配置协议解析的方式,即寄存器的解析方式,,配置信息有:Key,数据Index,源数据类型,数据库列类型,数据排列方式 

    一开始使用的方式是,从数据库读取出协议的配置,然后在接收到数据的时候,循环每个配置项根据配置-----解析数据------转换类型----存临时列表,,后来改进了一下,配置项存缓存,,数据库修改的时候,同时更新缓存。。

    但还是慢了一点,因为需一个配置大概是20-30个数据项,每一条数据都要for循环20-30次 ,再加上还有N个根据配置的数据类型去做转换的if判断,这么一套下来,也很耗时间,但待解析的数据量大的情况下,,相对也很耗资源。。。

    最后的觉得方案是:利用T4生成C#的class源码+运行时编译成类,数据直接扔class里直接解析出结果,不需要循环,也不需要if判断,因为在t4生成源码的时候,已经根据配置处理完了,因此节省了很多的时间。

    不过由于T4模板的IDE支持的很不好,不过好在运行时T4模板在IDE内生成出来的类是partial的,因此,可以把大部分的代码,放在外部的C#文件里。先来看数据项的配置信息:

 1  public class DataItem
 2         {
 3             /// 
 4             ///  数据项ID
 5             /// 
 6             public ObjectId DataItemID { set; get; }
 7 
 8             /// 
 9             /// 偏移量
10             /// 
11             public int Pos { set; get; }
12 
13             /// 
14             /// 大小
15             /// 
16             public int Size { set; get; }
17 
18             public int BitIndex { set; get; }
19 
20             /// 
21             /// 数据项数据库储存类型
22             /// 
23             public DbDataTypeEnum DbType { set; get; }
24 
25             /// 
26             /// 数据项协议源字节数组中的数据类型
27             /// 
28             public DataTypeEnum SourceType { set; get; }
29 
30             /// 
31             /// 计算因子
32             /// 
33             public decimal Factor { set; get; }
34 
35             public string Key { set; get; }
36         }
37     
38     /// 
39     /// 对应的数据库字段类型
40     /// 
41     public enum DbDataTypeEnum
42     {
43         Int32 = 0,
44 
45         Int64 = 1,
46 
47         Double = 2,
48 
49         DateTime = 3,
50 
51         Decimal = 4,
52 
53         Boolean = 5
54     }
55 
56     public enum DataTypeEnum
57     {
58         Int = 0,
59 
60         Short = 1,
61 
62         Datetime = 3,
63 
64         Long = 5,
65 
66         Decimal = 6,
67 
68         UInt = 7,
69 
70         Byte = 8,
71 
72         Boolean = 9,
73 
74         Bit = 10,
75 
76         UShort = 11,
77 
78         UByte = 12
79     }
View Code

   这里为什么要区分源数据和数据库数据类型呢?主要是因为设备一般是int,short,double,float等类型,但是,对应到数据库,有时候比如说使用mongodb,之类的数据库,不一定有完全匹配的,因此需要区分两种数据项,

   再来就是T4的模板  ProtocolExecuteTemplate.tt:

 1 <#@ template language="C#" #>
 2 <#@ assembly name="System.Core" #>
 3 <#@ assembly name="Kugar.Core.NetCore" #>
 4 <#@ assembly name="Kugar.Device.Service.BLL" #>
 5 <#@ assembly name="Kugar.Device.Service.Data" #>
 6 <#@ assembly name="MongoDB.Bson" #>
 7 
 8 <#@ import namespace="System.Linq" #>
 9 <#@ import namespace="System.Text" #>
10 <#@ import namespace="System.Collections.Generic" #>
11 <#@ import namespace="Kugar.Core.BaseStruct" #>
12 <#@ import namespace="MongoDB.Bson" #>
13 
14 using System;
15 using System.Text;
16 using Kugar.Core.BaseStruct;
17 using Kugar.Core.ExtMethod;
18 using Kugar.Core.Log;
19 using Kugar.Device.Service.Data.DTO;
20 using Kugar.Device.Service.Data.Enums;
21 using MongoDB.Bson;
22 
23 namespace Kugar.Device.Service.BLL
24 {
25     <#
26         var className="ProtocolExecutor_" + Protocol.Version.Replace('.','_') + "_" + this.GetNextClasID();
27  #>
28 
29 
30     public class <#=className #>:IProtocolExecutor
31     {
32         private string _version="";
33         private ObjectId _protocolID;
34         private readonly DateTime _baseDt=TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
35 
36         public <#=className #> (ObjectId protocolID, string version)
37         {
38             _version=version;
39             _protocolID=protocolID;
40         }
41 
42         public ObjectId ProtocolID {get{return _protocolID;}}
43 
44         public BsonDocument Execute(byte[] data, int startIndex)
45         {
46             BsonDocument bson=new BsonDocument();
47             <#
48                 foreach(var item in Protocol.Items){ #>
49             bson["<#=item.Key #>"]= <#= DecodeConfig(item,0) #>;
50 <#
51                 }
52  #>
53 
54             return bson;
55             
56         }    
57     }
58 }
View Code

   在直接在循环里输出解析后的语句,并且生成的类名记得后面加多一个随机数。。。。

   然后再加一个ProtocolExecuteTemplate.Part.cs的部分类,补全T4模板的功能,因为在T4里IDE支持的不好,,写代码确实难受,,没直接写C#舒服:

  1 public partial class ProtocolExecuteTemplate
  2     {
  3         private static int _classID = 0;
  4 
  5         public ProtocolExecuteTemplate(DTO_ProtocolDataItem protocol)
  6         {
  7             Protocol = protocol;
  8 
  9         }
 10 
 11         
 12 
 13         public DTO_ProtocolDataItem Protocol { set; get; }
 14 
 15         public string DecodeConfig(DTO_ProtocolDataItem.DataItem item,int startIndex)
 16         {
 17             var str = "";
 18 
 19             switch (item.SourceType)
 20             {
 21                 case DataTypeEnum.Int:
 22                     str = $"BitConverter.ToInt32(data,startIndex + {startIndex + item.Pos})";
 23                     break;
 24                 case DataTypeEnum.UInt:
 25                     str = $"BitConverter.ToUInt32(data,startIndex + {startIndex + item.Pos})";
 26                     break;
 27                 case DataTypeEnum.Short:
 28                     str = $"BitConverter.ToInt16(data,startIndex + {startIndex + item.Pos})";
 29                     break;
 30                 case DataTypeEnum.Long:
 31                     str= $"BitConverter.ToInt64(data,startIndex + {startIndex + item.Pos})";
 32                     break;
 33                 case DataTypeEnum.Byte:
 34                 case DataTypeEnum.UByte:
 35                 case DataTypeEnum.Bit:
 36                     str = $"data[startIndex + {startIndex + item.Pos}]";
 37                     break;
 38                 case DataTypeEnum.UShort:
 39                     str = $"BitConverter.ToUInt16(data,startIndex+{startIndex + item.Pos})";
 40                     break;
 41                 default:
 42                     throw new ArgumentOutOfRangeException();
 43             }
 44 
 45             if (item.SourceType==DataTypeEnum.Bit)
 46             {
 47                 return byteToBit(str, item.BitIndex);
 48             }
 49             else
 50             {
 51                 return valueTODBType(str, item.Factor, item.DbType);
 52             }
 53         }
 54 
 55         private string valueTODBType(string sourceValue, decimal factor, DbDataTypeEnum dbType)
 56         {
 57             switch (dbType)
 58             {
 59                 case DbDataTypeEnum.Int32:
 60                     return $" new BsonInt32({(factor > 0 ? $"(int)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
 61                 case DbDataTypeEnum.Int64:
 62                     return $" new BsonInt64({(factor > 0 ? $"(long)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
 63                 case DbDataTypeEnum.Double:
 64                     return $"new BsonDouble({(factor > 0 ? $"(double)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : $"(double){sourceValue}")})";
 65                 case DbDataTypeEnum.DateTime:
 66                     return $"new BsonDateTime(_baseDt.AddSeconds({sourceValue}))";
 67                 case DbDataTypeEnum.Decimal:
 68                     return $"new Decimal128({(factor > 0 ? $"(decimal)({factor}{getDecimalShortChar(factor)}  * {sourceValue})" : sourceValue)})";
 69                 default:
 70                     throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null);
 71             }
 72         }
 73 
 74         private string byteToBit(string data, int index)
 75         {
 76             switch (index)
 77             {
 78                 case 0:
 79                 {
 80 
 81                     return $"(({data} & 1) ==1)";//(data & 1) == 1;
 82                 }
 83                 case 1:
 84                 {
 85                     return $"(({data} & 2) ==1)";// (data & 2) == 2;
 86                     }
 87                 case 2:
 88                 {
 89                     return $"(({data} & 4) ==1)";//(data & 4) == 4;
 90                     }
 91                 case 3:
 92                 {
 93                     return $"(({data} & 8) ==1)";//(data & 8) == 8;
 94                     }
 95                 case 4:
 96                 {
 97                     return $"(({data} & 16) ==1)";//(data & 16) == 16;
 98                     }
 99                 case 5:
100                 {
101                     return $"(({data} & 32) ==1)";//(data & 32) == 32;
102                     }
103                 case 6:
104                 {
105                     return $"(({data} & 64) ==1)";//(data & 64) == 64;
106                     }
107                 case 7:
108                 {
109                     return $"(({data} & 128) ==1)";//(data & 128) == 128;
110                     }
111                 default:
112                     throw new ArgumentOutOfRangeException(nameof(index));
113             }
114 
115             return $"(({data} & {index + 1}) ==1)";
116         }
117         
118         /// 
119         /// 用于判断传入的fator是否需要使用deciaml进行运算,如果有小数点的,则是否decimal缩写m,,如果没有小数点,则使用普通的int类型
120         /// 
121         /// 
122         /// 
123         private string getDecimalShortChar(decimal value)
124         {
125             return (value % 1) == 0 ? "" : "m";
126         }
127 
128         public int GetNextClasID()
129         {
130             return Interlocked.Increment(ref _classID);
131         }
132     }
View Code

   这样,在运行时,即可直接生成可用于解析的类了,而且也不需要for循环判断,生成出来的类如:

 1     public class ProtocolExecutor_1_1_000
 2     {
 3         public BsonDocument Execute(byte[] data, int startIndex)
 4         {
 5             BsonDocument bson = new BsonDocument();
 6 
 7             bson["项目名1"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
 8             bson["项目名2"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
 9               。。。。。。。 //其他数据项
10 
11             return bson;
12         }
13     }

   到这一步,就可以根绝配置项生成出对应的C#代码了,剩下的就是动态编译的事情了、将该代码编译出运行时Type,然后传入数据----解析出数据

你可能感兴趣的:(与下位机或设备的通信解析优化的一点功能:T4+动态编译)