最近我在搞abi文件的解析工作,本来是用Python3写的数据提取和绘图(主要涉及struct、Biopython、matplotlib等包),但是如果想把脚本放到wpf项目中,试了几种方法,效果都不太好。所以我干脆用c#重写。
本文写的是struct包的unpack函数和calcsize函数的c#简单实现,以后会再加入pack函数并改进代码。
准备知识
python3的struct包,介绍看这里: https://docs.python.org/3/library/struct.html
举个例子:
_HEADFMT = ">H4sI2H3I"
这个是ABI文件中header的编码格式。其中:
1.> 表示大端,H是unsigned short,占2字节,s是char,占1字节,I是unsigned int,占4字节。
buffer = struct.pack(">ihb", 1, 2, 3)
# 大端模式,buffer为:(1在它所占4个字节的低端)
# b'\x00\x00\x00\x01\x00\x02\x03'
# 小端模式,buffer为:(1在它所占4个字节的高端)
buffer = struct.pack("<ihb", 1, 2, 3)
# b'\x01\x00\x00\x00\x02\x00\x03'
2.4s表示s重复四次,所以展开其实是 HssssIHHIII,所以一共占26个字节
由于abi文件中涉及的格式有限,所以我只给出一部分,如有需要可以自行补全。
简单实现
public class StructHelper {
// 不同数据类型对应的字节数目
public static Dictionary<char, int> charSizeDict = new Dictionary<char, int> {
{'c', 1},{'b', 1}, {'B', 1}, {'?', 1}, {'p', 1}, {'s', 1},
{'e', 2}, {'h', 2}, {'H', 2},
{'I', 4}, {'i', 4}, {'l', 4}, {'L', 4}, {'f', 4},
{'q', 8}, {'Q', 8}, {'d', 8}
};
/// <summary>
/// 计算符合该格式的字符串的总长度
/// </summary>
/// <param name="s">格式</param>
/// <returns></returns>
public int CalcSize(string s) {
int result = 0;
// 不考虑little-endian或big-endian的区别
int i = 1;
Regex regex = new Regex("[0-9]");
string num = "";
while (i < s.Length) {
if (regex.IsMatch(s.Substring(i, 1))) // 是数字
{
num += s[i];
}
else {
if (num.Length == 0)
{
result += charSizeDict[s[i]];
}
else {
int count = int.Parse(num);
result += charSizeDict[s[i]] * count;
num = "";
}
}
i += 1;
}
return result;
}
/// <summary>
/// 按类型将byte[]转为string
/// </summary>
/// <param name="type">类型:s/H/I/h/b/B/f</param>
/// <param name="raw">byte[]</param>
/// <returns></returns>
private string UnpackHelper(char type, byte[] raw) {
// 小端模式要翻转
if (BitConverter.IsLittleEndian)
Array.Reverse(raw);
string result = "";
switch (type) {
case 's':
byte[] raw0 = new byte[2];
if (BitConverter.IsLittleEndian)
{
raw0[0] = raw[0];
raw0[1] = 0;
}
else {
raw0[0] = 0;
raw0[1] = raw[0];
}
result = BitConverter.ToChar(raw0,0).ToString();
// 如果需要转ascii也可以下面这样
//result = System.Text.Encoding.ASCII.GetString(raw);
break;
case 'H':
byte[] raw2 = new byte[8];
// BitConverter.ToInt32是转为32位有符号整数,对于可能存在的4294967295这种数值会溢出
// 所以使用ToInt64。但是这需要补足8字节
if (BitConverter.IsLittleEndian)
{
raw2[0] = raw[0];
raw2[1] = raw[1];
raw2[2] = 0;
raw2[3] = 0;
raw2[4] = 0;
raw2[5] = 0;
raw2[6] = 0;
raw2[7] = 0;
}
else {
raw2[0] = 0;
raw2[1] = 0;
raw2[2] = 0;
raw2[3] = 0;
raw2[4] = 0;
raw2[5] = 0;
raw2[6] = raw[0];
raw2[7] = raw[1];
}
result = BitConverter.ToInt64(raw2, 0).ToString();
break;
...
case 'h': // short 2 bytes
result = BitConverter.ToUInt16(raw, 0).ToString();
break;
case 'f': // float, 4 bytes
// 这个注意不要用ToDouble
result = BitConverter.ToSingle(raw, 0).ToString();
break;
default:
break;
}
return result;
}
/// <summary>
/// 解码byte[]
/// </summary>
/// <param name="format">格式</param>
/// <param name="buffer">byte[]</param>
/// <returns></returns>
public List<string> Unpack(string format, byte[] buffer) {
List<string> result = new List<string>();
char first = format[0];
bool isBigEndian = false;
if (first.Equals('>') || first.Equals('!')) {
isBigEndian = true;
}
Regex regex = new Regex("[0-9]");
string num = "";
int pos = 0;
// 暂时不考虑小端
if (isBigEndian) {
int i = 1;
while (i < format.Length) {
char curChar = format[i];
// 如果是数字
if (regex.IsMatch(format.Substring(i, 1)))
{
num += curChar;
}
else
{
if (charSizeDict.ContainsKey(curChar) == false) {
Console.WriteLine("curChar: " + curChar);
}
int singleSize = charSizeDict[curChar];
// 单一字符
if (num.Length == 0)
{
// 构建byte[]
byte[] raw = new byte[singleSize];
int k = 0;
for (int j = pos; j < pos + singleSize; j++) {
raw[k] = buffer[j];
k += 1;
}
// 计算结果
result.Add(UnpackHelper(curChar, raw));
// 移动buffer中的位置指针
pos += charSizeDict[curChar];
}
else
{
int count = int.Parse(num);
string tmp = "";
for (int m = 0; m < count; m++)
{
byte[] raw = new byte[singleSize];
int k = 0;
for (int j = pos; j < pos + singleSize; j++)
{
raw[k] = buffer[j];
k += 1;
}
// I和H是各自分开的
if (curChar != 's')
{
result.Add(UnpackHelper(curChar, raw));
}
else { // s的话是要count个合为一个的
tmp += UnpackHelper(curChar, raw);
}
pos += charSizeDict[curChar];
}
if (tmp.Length > 0) {
result.Add(tmp);
//tmp = "";
}
// 清空num
num = "";
}
}
// 移动format中的指针
i += 1;
}
}
return result;
}
}