使用Golang为程序注入外部数据的最佳实践

使用Golang为程序注入外部数据的最佳实践 你好,我想创建一个函数,用于两种目的:将数据写入单个变量或切片中。这里的数据指的是类似FFI(外部函数接口)的情况,例如来自数据库或硬件的数据,因此我需要导入“unsafe”包并使用这类函数。

数据源只是一个指向外部数据的uintptr指针。

Foo(&v) 必须将一个元素写入 vFoo(&s)Foo(s) 必须将 len(s) 个元素写入切片 s

关键点是使用一个函数,这样就不会有人意外地使用 Foo 将12字节的输入数据写入切片头部,因为 Foo(&v) 将始终存在,并且为了写入数据,它会丢弃类型(安全性)。

所以,我可以参考 binary.Write 的做法,复用已有的实现。但直到今天,那个包给我带来了太多问题。很自然,除非我添加更多代码,否则它无法处理平台相关大小的整数,这让我很烦恼。而今天,它甚至无法处理一个简单的 float32。我实在不明白了。通常人们会学习如何使用某个东西,但我却完全忘记了那东西是怎么工作的。

你有什么想法,可以将外部数据导入到单个变量和切片中,并使其成为一个良好的编程接口吗?我的意思是,我并不真的认为 binary 模块很好。当它失败时,我根本不明白它做了什么——回溯信息里有太多东西了。

任何方法都可以,只要不是 binary 包的那种方式就行,它显然会因为各种原因在运行时失败。

非常感谢!

func main() {
    fmt.Println("hello world")
}

更多关于使用Golang为程序注入外部数据的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

那是我,我是Tomas,我现在又用回我的新手账号了。我想我可能没有解释清楚。这个问题包含几个方面,我考虑了很多事情。其中一些可能是因为Go主要用于特定领域(服务器等,而我试图用它来开发桌面软件),有些则不是。

  1. 第一点是,在Go中走标准路线会不会让人望而生畏?我的意思是,我从未尝试过Gob、Protobuf以及数据序列化、通信协议或相关主题。我对如何编写底层代码有一些了解,但对这类现代技术并不真正熟悉。

  2. 我模糊提到的问题是,我对在尝试序列化结构体并保存到磁盘时遇到运行时错误感到陌生。我熟悉的是C/C++,它们也会因为各种原因在运行时崩溃。这很不同。实际上在Go中,感觉在运行时失败是不必要的,但我确信有充分的理由。

  3. 我不太明白Go语言规范在提到字段对齐“至少为1”时到底是什么意思,他们在那里简要介绍了结构体的内存布局。我从他们简短的描述中读到的是,我可以通过查找最高对齐值来找出结构体的默认对齐方式,但我在Go的这部分是新手,并不了解。在C语言中,我只是依赖于这样一个事实:对齐方式基本上只在我为结构体使用pragma指定打包方式,或者在32位与64位系统之间切换时才会改变。但我没有深入研究Go。

  4. 当组件A需要打包方式A’,而我所选编程语言中最喜欢的语言结构引导我使用打包方式B时,我是否会后悔在时间关键的部分给了语言最大的打包控制权?例如,我最终可能需要在显卡需要这些数据而我的电脑已经在努力渲染游戏的情况下,重新打包一个浮点数数组。这是否是一个人们会想要回归C++的现实场景?

  5. 在某种程度上,在Go中保存和加载数据可能会变得更加细粒度。例如,你可能需要用两行代码来保存两个float32值(x, y),而在另一种语言中,你可能更愿意用一行代码传递整个结构体。Go是否会导致你编写一些代码时,希望它的行为能像另一种语言那样?编写代码可能相当费事。

这就是我对Go与C和C++的担忧。另外,我不擅长处理包含两种或更多语言的项目。其中的摩擦。哦,还有,我对Go中类型参数(typeparams)的实现方式并不十分信服。泛型加上有些老旧的RTTI对于我这种崇尚简单的风格来说变得太复杂了,而Go在大部分时间其实都支持这种简单风格。

致以最诚挚的问候,感谢阅读。

更多关于使用Golang为程序注入外部数据的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中处理外部数据注入时,unsafe包确实是必要的选择。以下是针对你需求的实现方案:

package main

import (
    "fmt"
    "unsafe"
)

// Foo 将外部数据写入变量或切片
func Foo(dst interface{}, src uintptr) error {
    switch v := dst.(type) {
    case *int8:
        *(*int8)(unsafe.Pointer(v)) = *(*int8)(unsafe.Pointer(src))
    case *uint8:
        *(*uint8)(unsafe.Pointer(v)) = *(*uint8)(unsafe.Pointer(src))
    case *int16:
        *(*int16)(unsafe.Pointer(v)) = *(*int16)(unsafe.Pointer(src))
    case *uint16:
        *(*uint16)(unsafe.Pointer(v)) = *(*uint16)(unsafe.Pointer(src))
    case *int32:
        *(*int32)(unsafe.Pointer(v)) = *(*int32)(unsafe.Pointer(src))
    case *uint32:
        *(*uint32)(unsafe.Pointer(v)) = *(*uint32)(unsafe.Pointer(src))
    case *float32:
        *(*float32)(unsafe.Pointer(v)) = *(*float32)(unsafe.Pointer(src))
    case *int64:
        *(*int64)(unsafe.Pointer(v)) = *(*int64)(unsafe.Pointer(src))
    case *uint64:
        *(*uint64)(unsafe.Pointer(v)) = *(*uint64)(unsafe.Pointer(src))
    case *float64:
        *(*float64)(unsafe.Pointer(v)) = *(*float64)(unsafe.Pointer(src))
    case *complex64:
        *(*complex64)(unsafe.Pointer(v)) = *(*complex64)(unsafe.Pointer(src))
    case *complex128:
        *(*complex128)(unsafe.Pointer(v)) = *(*complex128)(unsafe.Pointer(src))
    case []int8:
        copyFromPtr(v, src, unsafe.Sizeof(int8(0)))
    case []uint8:
        copyFromPtr(v, src, unsafe.Sizeof(uint8(0)))
    case []int16:
        copyFromPtr(v, src, unsafe.Sizeof(int16(0)))
    case []uint16:
        copyFromPtr(v, src, unsafe.Sizeof(uint16(0)))
    case []int32:
        copyFromPtr(v, src, unsafe.Sizeof(int32(0)))
    case []uint32:
        copyFromPtr(v, src, unsafe.Sizeof(uint32(0)))
    case []float32:
        copyFromPtr(v, src, unsafe.Sizeof(float32(0)))
    case []int64:
        copyFromPtr(v, src, unsafe.Sizeof(int64(0)))
    case []uint64:
        copyFromPtr(v, src, unsafe.Sizeof(uint64(0)))
    case []float64:
        copyFromPtr(v, src, unsafe.Sizeof(float64(0)))
    default:
        return fmt.Errorf("unsupported type: %T", dst)
    }
    return nil
}

// copyFromPtr 将数据从指针复制到切片
func copyFromPtr(slice interface{}, src uintptr, elemSize uintptr) {
    slicePtr := unsafe.Pointer((*[2]uintptr)(unsafe.Pointer(&slice))[1])
    length := (*[3]uintptr)(unsafe.Pointer(&slice))[2]
    
    // 计算要复制的总字节数
    totalBytes := length * elemSize
    
    // 使用memcpy风格复制
    dstSlice := unsafe.Slice((*byte)(slicePtr), totalBytes)
    srcSlice := unsafe.Slice((*byte)(unsafe.Pointer(src)), totalBytes)
    copy(dstSlice, srcSlice)
}

// 更类型安全的版本,使用泛型(Go 1.18+)
func FooGeneric[T any](dst *T, src uintptr) {
    *dst = *(*T)(unsafe.Pointer(src))
}

func FooSlice[T any](dst []T, src uintptr) {
    if len(dst) == 0 {
        return
    }
    
    elemSize := unsafe.Sizeof(dst[0])
    totalBytes := uintptr(len(dst)) * elemSize
    
    dstPtr := unsafe.Pointer(&dst[0])
    srcPtr := unsafe.Pointer(src)
    
    dstSlice := unsafe.Slice((*byte)(dstPtr), totalBytes)
    srcSlice := unsafe.Slice((*byte)(srcPtr), totalBytes)
    copy(dstSlice, srcSlice)
}

// 使用示例
func main() {
    // 模拟外部数据
    var externalData [16]byte
    for i := range externalData {
        externalData[i] = byte(i)
    }
    
    srcPtr := uintptr(unsafe.Pointer(&externalData[0]))
    
    // 写入单个变量
    var singleInt int32
    Foo(&singleInt, srcPtr)
    fmt.Printf("Single int: %d\n", singleInt)
    
    // 写入切片
    slice := make([]int32, 4)
    Foo(slice, srcPtr)
    fmt.Printf("Slice: %v\n", slice)
    
    // 使用泛型版本
    var singleFloat float32
    FooGeneric(&singleFloat, srcPtr)
    fmt.Printf("Generic single float: %f\n", singleFloat)
    
    floatSlice := make([]float32, 2)
    FooSlice(floatSlice, srcPtr)
    fmt.Printf("Generic slice: %v\n", floatSlice)
}

对于需要处理结构体的场景:

// 处理结构体的版本
type MyStruct struct {
    ID    uint32
    Value float64
    Flag  bool
}

func FooStruct(dst *MyStruct, src uintptr) {
    size := unsafe.Sizeof(*dst)
    dstBytes := unsafe.Slice((*byte)(unsafe.Pointer(dst)), size)
    srcBytes := unsafe.Slice((*byte)(unsafe.Pointer(src)), size)
    copy(dstBytes, srcBytes)
}

func FooStructSlice(dst []MyStruct, src uintptr) {
    if len(dst) == 0 {
        return
    }
    
    elemSize := unsafe.Sizeof(dst[0])
    totalBytes := uintptr(len(dst)) * elemSize
    
    dstPtr := unsafe.Pointer(&dst[0])
    srcPtr := unsafe.Pointer(src)
    
    dstSlice := unsafe.Slice((*byte)(dstPtr), totalBytes)
    srcSlice := unsafe.Slice((*byte)(srcPtr), totalBytes)
    copy(dstSlice, srcSlice)
}

这个实现直接操作内存,避免了binary包的复杂性和运行时开销,同时通过类型检查确保安全性。

回到顶部