提问人:toudi 提问时间:9/18/2023 最后编辑:Marco van de Voorttoudi 更新时间:9/20/2023 访问量:58
希望使用 Reader 界面,以使我的二进制文件导入代码更干净
hoping to use the Reader interface in order to make my binary files import code cleaner
问:
我很清楚这不适用于可变参数类型(如字符串)。我的问题如下:encoding/binary
我有一些旧的二进制文件,是用帕斯卡语言编写的程序制作的。如果不是字符串类型,我本来可以很容易地阅读它。Pascal 允许您声明具有最大长度的字符串(长度不能超过 255 个字符),但它将其保存到字节数较少的文件中以节省磁盘空间。例如,如果我有以下声明:
var a_string: string[200];
a_string := "AAAA"
然后,该文件将以以下字节结束:
0x04 0x41 0x41 0x41 0x41
^^^^ ^^^^^^^^^^^^^^^^^^^
| +---------- content
+---------------------- length
到目前为止一切顺利,没有什么特别有争议的。我的问题是我试图将它连接到二进制文件。读取,它显然失败了,因为二进制。读取只能解析固定大小的结构。然而,我的问题是,阅读这种结构的唯一方法是以下方式:
https://go.dev/play/p/OZRgaDqh9cc
当然有效,但看起来很丑。想象一下,结构中有 13 个字段,而只有 3 个字段是 pascal 字符串类型的。我错过了什么吗?换句话说,是否有一些我无法谷歌搜索的阅读器界面?基本上,我在这里唯一要做的就是覆盖字节的行为。此自定义类型的读取器。
我能想到的有两种方法。首先是通过反射迭代结构,并相应地对恰好是帕斯卡字符串类型的字段做出反应。但这似乎是一个巨大的矫枉过正。
第二种方法:
1/ 将我可以用常规阅读的“纯”类型包装在子结构中binary.Read
2/ 用于获取缓冲区内的“当前”位置totalBytesRead, _ = reader.Seek(0, os.SEEK_CUR)
3/ 使用 custom 读取 pascal 字符串(将返回 )并递增.Read()
bytesRead
totalBytesRead
bytesRead
4/ 使用并继续使用reader.Seek(totalBytesRead, os.SEEK_SET)
binary.Read
但它似乎仍然只是......尴尬,我的直觉告诉我我做错了
我还尝试过:
- 创建一个这样的类型别名,希望欺骗编码/二进制文件,使其认为它只需要分配一个更大的缓冲区:
type PString [255]byte
- 查看二进制协议的现有实现。因此,例如,我看了一下这段代码: https://github.com/elcapitansam/gostruct/tree/master 但这是一个略有不同的问题,因为它解码为“纯”接口。我的问题略有不同,因为我已经知道我要解析的目标接口是什么。
答:
我尝试了反思方法,这是我能够实现的解决方案:
https://go.dev/play/p/MPBOmQAmECl
(它使用编解码器的动态映射,而不是对函数内的所有内容进行硬编码)
对我来说,这似乎仍然过于复杂。我的意思是,我一直认为,如果其他一切都失败了,使用反射将是解决方案,但我的问题似乎很容易,以至于这个解决方案似乎是错误的,至少在直觉上是这样。另外,在这种方法中,仍然需要为所有 int / uint 和 float 类型实现解码器函数
package main
import (
"bytes"
"encoding/binary"
"fmt"
"reflect"
)
// decoder function receives a source byte slice and a destination pointer
// it returns number of bytes that have been parsed and optionally an error
type Decoder func(src []byte, dest reflect.Value, structField reflect.StructField) (int, error)
type BinaryDecoder struct {
Codecs map[reflect.Kind]Decoder
}
func (d *BinaryDecoder) Unpack(src []byte, dst interface{}) (int, error) {
return d.unpackRecursive(src, dst, reflect.StructField{})
}
func (d *BinaryDecoder) unpackRecursive(src []byte, dst interface{}, structField reflect.StructField) (int, error) {
var bytesRead int = 0
var err error
dstValue := reflect.ValueOf(dst)
dstType := dstValue.Type()
if dstValue.Kind() == reflect.Ptr {
dstValue = dstValue.Elem()
dstType = dstType.Elem()
}
valueType := dstValue.Kind()
switch valueType {
case reflect.Struct:
// let's unpack the struct field by field.
for i := 0; i < dstValue.NumField(); i += 1 {
structField := dstValue.Field(i)
fmt.Printf("%d'th field has a name of %s and is of type %v\n", i, structField.Type().Name(), structField.Kind())
fmt.Printf("bin tag: %v\n", dstType.Field(i).Tag.Get("bin"))
if bytesRead, err = d.unpackRecursive(src, structField.Addr().Interface(), dstType.Field(i)); err != nil {
return -1, fmt.Errorf("unable to unpack struct: %v", err)
}
fmt.Printf("Read %d bytes from %s\n", bytesRead, structField.Type().Name())
src = src[bytesRead:]
}
case reflect.Array:
declaredSize := dstType.Size()
fmt.Printf("begin to unpack array; declaredSize=%d\n", declaredSize)
for i := 0; i < int(declaredSize); i += 1 {
if bytesRead, err = d.unpackRecursive(src, dstValue.Index(i).Addr().Interface(), structField); err != nil {
return -1, fmt.Errorf("unable to unpack array at position %d: %v", i, err)
}
src = src[bytesRead:]
}
default:
decoder, exists := d.Codecs[valueType]
if !exists {
return -1, fmt.Errorf("unsupported value type: %v", valueType)
}
return decoder(src, dstValue, structField)
}
return bytesRead, nil
}
type NestedStruct struct {
PasString string `bin:"p"`
}
type BinaryStruct struct {
IntField uint16
Nested NestedStruct
Array [10]byte
}
func decodeUint8(src []byte, dest reflect.Value, structField reflect.StructField) (int, error) {
dest.SetUint(uint64(src[0]))
return 1, nil
}
func decodeUint16(src []byte, dest reflect.Value, structField reflect.StructField) (int, error) {
var tmpVal uint16
if err := binary.Read(bytes.NewReader(src), binary.LittleEndian, &tmpVal); err != nil {
return -1, fmt.Errorf("unable to decode uint16: %v", err)
}
dest.SetUint(uint64(tmpVal))
return 2, nil
}
func decodeString(src []byte, dest reflect.Value, structField reflect.StructField) (int, error) {
if structField.Tag.Get("bin") != "p" {
return -1, fmt.Errorf("unsupported string type")
}
declaredLength := int(src[0])
dest.SetString(string(src[1 : declaredLength+1]))
return declaredLength + 1, nil
}
func main() {
var srcBytes = []byte{
0x00, 0x01, // uint16
0x04, 0x41, 0x41, 0x41, 0x41, // string
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // array of 10 bytes
}
var bin BinaryStruct
var numBytes int
var err error
decoder := &BinaryDecoder{
Codecs: map[reflect.Kind]Decoder{
reflect.Uint8: decodeUint8,
reflect.Uint16: decodeUint16,
reflect.String: decodeString,
},
}
numBytes, err = decoder.Unpack(srcBytes, &bin)
if err != nil {
fmt.Printf("Error unpacking structure: %v\n", err)
}
fmt.Printf("Read %d bytes; end structure: %+v\n", numBytes, bin)
}
评论
encoding/binary