Kotlin:编码和解码二进制结构数据(Kotlin 相当于 Python 的 struct.pack 和 struct.unpack)

发布于 2025-01-16 21:03:56 字数 1430 浏览 3 评论 0原文

对于基于 C 的嵌入式硬件模块,配置结构由特定布局中的多个字段组成,例如以这个 8 字节结构为例:

OffsetDatatypeField
0UInt8fieldA
1UInt16some_value
3UInt32another_value
7UInt8aByte

This 8-字节配置可以通过 NFC 通信读取和写入。因此,有一个 Android 应用程序可以读取此数据(作为 8 个字节的序列)并将其写回,以便嵌入式硬件模块上的固件(用 C 编写)可以“理解”它。

现在的任务是将例如 12 ab cd 04 fe ff 56 77(little-endian!)的 8 字节序列解码为解码值:

字段要解码的字节已解码的人类可读数字
fieldA120x12
some_valueab cd0xCDAB
another_value04 fe ff 560x56FFFE04
aByte770x77

请注意,这是一个 Kotlin 或 Java 问题,不是 C 问题;)

现在我的问题是找到一种 Kotlin 方法来将这样的二进制结构解码为相应的值(如上所示),以便可以将这些值呈现给应用程序用户。并且,将这些值(在用户编辑一些值之后)编码回 8 字节的二进制结构。

请注意,字节顺序也是一个问题。一般来说,目标系统是小端ARM,Android应用程序也在小端系统上运行,所以可能没有问题。不过,这是巧合,我想明确指出这一点。

使用必要的显式字节序转换将数字解码/编码为此类字节的 Kotlin 方式是什么?

如果是针对 Python,则 struct 库及其 packunpack 函数对于此类任务来说是完美。但如何在 Kotlin 中做到这一点呢?我很想看到这样的功能......

For a C based embedded hardware module, the configuration structure is made up of several fields in a particular layout, e.g. take this 8-byte struct for example:

OffsetDatatypeField
0UInt8fieldA
1UInt16some_value
3UInt32another_value
7UInt8aByte

This 8-byte config can be read and written via NFC communication. So there is an Android app which reads this data (as a sequence of 8 bytes) and can write it back, so that the firmware on the embedded hardware module (written in C) can "understand" it.

The task is now to decode the 8-byte sequence of e.g. 12 ab cd 04 fe ff 56 77 (little-endian!) into the decoded values:

FieldBytes to be decodedDecoded, human-readable number
fieldA120x12
some_valueab cd0xCDAB
another_value04 fe ff 560x56FFFE04
aByte770x77

Note that this is a Kotlin or Java question, no C question ;)

Now my question is about finding a Kotlin way to decode such a binary struct into the respective values (as shown above), so that the values can be presented to the app user. And, encode the values (after the user would have edited some values) back into the binary structure of 8 bytes.

Note that endianness is also an issue. In general, the target system is a little-endianed ARM and the Android app also runs on a little-endian system, so there might be no issue. However, this is by coincidence, and I would like to make this explicit.

What could be a Kotlin way of decoding/encoding numbers into such bytes, using explicit endian conversion of necessary?

If it was for Python, the struct library with its pack and unpack function are PERFECT for such tasks. But how to do this in Kotlin? I would love to see exactly such functions ...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

埋葬我深情 2025-01-23 21:03:56

我能想到的最好的方法是将字节数组包装在 ByteBuffer 并一一读取。

假设您有:

data class SomeStructure(
    val fieldA: UByte,
    val someValue: UShort,
    val anotherValue: UInt,
    val aByte: UByte,
)

您可以执行以下操作:

val byteArray: ByteArray = ....
val buffer = ByteBuffer.wrap(byteArray).order(ByteOrder.LITTLE_ENDIAN)
val someStruct = SomeStructure(
    buffer.get().toUByte(),
    buffer.getShort().toUShort(),
    buffer.getInt().toUInt(),
    buffer.get().toUByte(),
)
println(someStruct)

请注意,ByteBuffergetgetXXX 方法会提前“读取”位置,从而改变 ByteBuffer,因此,如果您在创建 SomeStructure 后出于某种原因想要再次重新读取缓冲区,您应该翻转它,或者只是创建一个新的字节缓冲区。

您还可以将其设为 SomeStructure 的辅助构造函数。

constructor(buffer: ByteBuffer): this(
    buffer.get().toUByte(),
    buffer.getShort().toUShort(),
    buffer.getInt().toUInt(),
    buffer.get().toUByte(),
)

这样,您甚至可以支持从字节缓冲区读取引用 SomeStructure 的数据类:

data class SomeOtherStructure(
    val struct1: SomeStructure,
    val struct2: SomeStructure,
) {
    constructor(buffer: ByteBuffer): this(
        SomeStructure(buffer), // recall that this advances the current position of the buffer
        SomeStructure(buffer)
    )
}

The best I can think of is to just wrap the byte array in a ByteBuffer and read it one by one.

Suppose you have:

data class SomeStructure(
    val fieldA: UByte,
    val someValue: UShort,
    val anotherValue: UInt,
    val aByte: UByte,
)

You can do:

val byteArray: ByteArray = ....
val buffer = ByteBuffer.wrap(byteArray).order(ByteOrder.LITTLE_ENDIAN)
val someStruct = SomeStructure(
    buffer.get().toUByte(),
    buffer.getShort().toUShort(),
    buffer.getInt().toUInt(),
    buffer.get().toUByte(),
)
println(someStruct)

Note that the get and getXXX methods of ByteBuffer advances the "reading" position, hence mutating the ByteBuffer, so if you want to re-read the buffer again for whatever reason after creating the SomeStructure, you should flip it, or just create a new byte buffer.

You could also make this a secondary constructor of SomeStructure.

constructor(buffer: ByteBuffer): this(
    buffer.get().toUByte(),
    buffer.getShort().toUShort(),
    buffer.getInt().toUInt(),
    buffer.get().toUByte(),
)

This way, you could even support reading data classes with references to SomeStructures from byte buffers:

data class SomeOtherStructure(
    val struct1: SomeStructure,
    val struct2: SomeStructure,
) {
    constructor(buffer: ByteBuffer): this(
        SomeStructure(buffer), // recall that this advances the current position of the buffer
        SomeStructure(buffer)
    )
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文