1 简介
本文以一个demo程序介绍如何使用C# (Winform)解析DBC文件,有关DBC文件的内容,请参考:DBC文件格式文档【翻译】
2 程序结构
![](https://i-blog.csdnimg.cn/blog_migrate/826055b07d64aeccb4785bad4cff232b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/87485a9d03f1c568e5a6d0512c6c0e35.png)
3 加载DBC
在本demo程序中,使用了一个dll库加载DBC文件,这个库为FileLoaderLib,是对C#自带类FileStream和StreamReader的简单封装,只有一个Load函数,用来将文本中的所有内容读入字符串待用。
/// <summary>
/// 加载文本文件到字符串
/// </summary>
/// <param name="path">文件路径</param>
/// <param name="stringOut">输出的字符串</param>
/// <returns></returns>
public static int Load(string path, ref string stringOut)
{
FileStream fs = null;
StreamReader sr = null;
try
{
fs = new FileStream(path, FileMode.Open, FileAccess.Read);
sr = new StreamReader(fs);
stringOut = sr.ReadToEnd();
}
catch (Exception en)
{
if (sr != null)
{
sr.Close();
}
if (fs != null)
{
fs.Close();
}
return ExceptionHandler.Report("加载文件失败" + en.Message);
}
if (sr != null)
{
sr.Close();
}
if (fs != null)
{
fs.Close();
}
return 0;
}
4 解析
解析过程在DbcLib库的DbcHelper类中的Parse函数中实现,首先调用FileLoader.Load将DBC读到fileBuffer,然后调用StrToDbeFile函数将fileBuffer解析到dbcFile,dbcFile是用来描述DBC的类,其中定义了消息、信号等内容,注意本程序只解析了常用部分,如果你用到剩余部分,可以自行修改,稍后我会上传完整工程。
public int Parse(string _path)
{
int err = 0;
path = _path;
err = FileLoader.Load(path, ref fileBuffer);
err = StrToDbeFile();
return err;
}
private int StrToDbeFile()
{
int err = 0;
string[] bufferAry = null;
dbcFile = new DbcFile();
if (fileBuffer == null)
{
if (fileBuffer == "")
{
ExceptionHandler.Report("Dbc文件为空");
}
}
bufferAry = fileBuffer.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (bufferAry.Length < 3)
{
return ExceptionHandler.Report("Dbc文件格式错误");
}
int lineNum = bufferAry.Length;
bool isMessageValid = false;
for (int i = 0; i < lineNum; i++)
{
string[] lineAry = bufferAry[i].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (lineAry.Length < 1)
{
return ExceptionHandler.Report("Dbc文件行格式错误");
}
switch (lineAry[0])
{
case "VAL_:":
{
break;
}
case "CM_:":
{
Comment cmt = new Comment();
cmt.comment = bufferAry[i];
dbcFile.comments.Add(cmt);
break;
}
case "BU_:":
{
for (int j = 1; j < (lineAry.Length); j++)
{
dbcFile.nodes.Add(lineAry[j]);
}
break;
}
case "BO_":
{
Message message = new Message();
uint id = Convert.ToUInt32(lineAry[1]);
//跳过默认的消息
if (id == 0xC0000000)
{
isMessageValid = false;
break;
}
else
{
isMessageValid = true;
}
//最高位为1的为扩展帧
if ((id & 0x80000000) != 0)
{
id &= 0x7FFFFFFF;
message.isExternId = true;
}
else
{
message.isExternId = false;
}
message.messgeId = id;
message.messageName = lineAry[2].Substring(0, lineAry[2].Length - 1);
message.messageSize = Convert.ToUInt32(lineAry[3]);
message.transmitter = lineAry[4];
dbcFile.messages.Add(message);
break;
}
case "SG_":
{
if (isMessageValid)
{
uint byteOffset = 0;
Signal signal = new Signal();
signal.signalName = lineAry[1];
if (lineAry[2] == ":")
{
signal.multiplexerIndicator = -2;
byteOffset = 0;
}
else
{
byteOffset = 1;
if (lineAry[2][0] == 'M')
{
signal.multiplexerIndicator = -1;
}
else if (lineAry[2][0] == 'm')
{
signal.multiplexerIndicator = Convert.ToInt32(lineAry[2].Substring(1, lineAry[2].Length - 1));
}
else
{
return ExceptionHandler.Report("Dbc信号格式错误");
}
}
string[] sp = lineAry[3 + byteOffset].Split(new char[] { '|', '@' }, StringSplitOptions.RemoveEmptyEntries);
signal.startBit = Convert.ToUInt32(sp[0]);
signal.signalSize = Convert.ToUInt32(sp[1]);
if (sp[2][0] == '0')
{
signal.byteOrder = 0;
}
else if (sp[2][0] == '1')
{
signal.byteOrder = 1;
}
if (sp[2][1] == '+')
{
signal.valueType = 0;
}
else if (sp[2][1] == '-')
{
signal.valueType = 1;
}
string[] sp1 = lineAry[4 + byteOffset].Split(new char[] { '(', ',', ')' }, StringSplitOptions.RemoveEmptyEntries);
signal.factor = Convert.ToDouble(sp1[0]);
signal.offset = Convert.ToDouble(sp1[1]);
string[] sp2 = lineAry[5 + byteOffset].Split(new char[] { '[', '|', ']' }, StringSplitOptions.RemoveEmptyEntries);
signal.minimum = Convert.ToDouble(sp2[0]);
signal.maximum = Convert.ToDouble(sp2[1]);
signal.uintStr = lineAry[6 + byteOffset];
signal.receivers = lineAry[7 + byteOffset].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
dbcFile.messages[dbcFile.messages.Count - 1].signals.Add(signal);
}
break;
}
}
}
return err;
}
5 显示
显示部分在主窗体MainWid类中实现,左边用了一个Treeview显示DBC中的节点和消息列表,右侧用Listview选中的消息中的信号列表,这两个控件放在splitContainer控件中,布局如下图:
当点击浏览按钮选择要解析的DBC文件后,会执行dbcHelper.Parse函数,完成DBC解析,然后调用UpdateDbcTreeview函数将节点、消息列表加载到左侧Treeview中。
private void UpdateDbcTreeview()
{
for (int i = 0; i < treeView1.Nodes.Count; i++)
{
treeView1.Nodes[i].Nodes.Clear();
}
for (int i = 0; i < dbcHelper.dbcFile.nodes.Count; i++)
{
treeView1.Nodes[0].Nodes.Add(dbcHelper.dbcFile.nodes[i]);
}
for (int i = 0; i < dbcHelper.dbcFile.messages.Count; i++)
{
treeView1.Nodes[1].Nodes.Add(dbcHelper.dbcFile.messages[i].messageName);
}
treeView1.ExpandAll();
}
鼠标点击想要查看的消息名,触发Treeview的AfterSelect事件,在事件中调用UpdateDbcListview函数,将信号列表显示在ListView中
private void UpdateDbcListview(int index)
{
ListViewItem item;
listView1.BeginUpdate();
listView1.Items.Clear();
for (int i = 0; i < dbcHelper.dbcFile.messages[index].signals.Count; i++)
{
item = new ListViewItem();
item.Text = dbcHelper.dbcFile.messages[index].signals[i].signalName;
item.SubItems.Add(dbcHelper.dbcFile.messages[index].signals[i].startBit.ToString());
item.SubItems.Add(dbcHelper.dbcFile.messages[index].signals[i].signalSize.ToString());
item.SubItems.Add(ByteOrder[dbcHelper.dbcFile.messages[index].signals[i].byteOrder]);
item.SubItems.Add(ValueType[dbcHelper.dbcFile.messages[index].signals[i].valueType]);
item.SubItems.Add(dbcHelper.dbcFile.messages[index].signals[i].factor.ToString());
item.SubItems.Add(dbcHelper.dbcFile.messages[index].signals[i].offset.ToString());
item.SubItems.Add(dbcHelper.dbcFile.messages[index].signals[i].minimum.ToString());
item.SubItems.Add(dbcHelper.dbcFile.messages[index].signals[i].maximum.ToString());
item.SubItems.Add(dbcHelper.dbcFile.messages[index].signals[i].uintStr);
listView1.Items.Add(item);
}
listView1.EndUpdate();
}
效果如下图:
这就是DBC文件解析demo的所有内容了,具体的应用大家可以自由发挥,有问题可以留言交流。