You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用sdl.PushEvent传递非空Go结构体触发Cgo指针错误的解决方法

问题描述

以下是触发错误的代码:

package main

import (
    "unsafe"

    "github.com/veandco/go-sdl2/sdl"
)

type MyType struct {
    A int
}

func main() {
    if err := sdl.Init(sdl.INIT_EVENTS); err != nil {
        panic(err)
    }

    ty := sdl.RegisterEvents(1)
    sdl.PushEvent(&sdl.UserEvent{
        Type:      ty,
        Timestamp: sdl.GetTicks(),
        Data1:     unsafe.Pointer(&MyType{}),
    })
}

尝试通过sdl.PushEvent传递Go结构体时,只要结构体非空(包含任意成员)就会触发**"Cgo argument has Go pointer to Go pointer"**错误;空结构体可正常运行,但业务场景下无法使用空结构体。当前使用Go 1.19版本。


解决办法

方法1:将结构体分配到C堆内存

通过C标准库的malloc分配内存,把Go结构体的数据拷贝到C内存中,传递给SDL的就是纯C指针,不会触发Go的指针检查:

package main

import (
    "unsafe"

    "C"
    "github.com/veandco/go-sdl2/sdl"
)

type MyType struct {
    A int
}

func main() {
    if err := sdl.Init(sdl.INIT_EVENTS); err != nil {
        panic(err)
    }
    defer sdl.Quit()

    // 分配C堆内存,大小匹配MyType
    cData := C.malloc(C.size_t(unsafe.Sizeof(MyType{})))
    defer C.free(cData) // 务必释放内存,避免泄漏

    // 将Go结构体拷贝到C内存
    myData := MyType{A: 42}
    *(*MyType)(cData) = myData

    ty := sdl.RegisterEvents(1)
    err := sdl.PushEvent(&sdl.UserEvent{
        Type:      ty,
        Timestamp: sdl.GetTicks(),
        Data1:     cData,
    })
    if err != nil {
        panic(err)
    }
}

注意:接收事件时,需要把C指针转换回Go指针读取数据,用完后要确保释放C内存(如果不在Push的goroutine里释放,需保证后续能正确清理)。

方法2:用全局映射存储数据,事件传递ID

创建带锁的全局map存储结构体实例,事件只传递对应的ID,接收时通过ID从map中取出数据:

package main

import (
    "sync"
    "unsafe"

    "github.com/veandco/go-sdl2/sdl"
)

type MyType struct {
    A int
}

var (
    dataMap = make(map[uintptr]MyType)
    mapLock sync.Mutex
    nextID  uintptr = 1
)

func main() {
    if err := sdl.Init(sdl.INIT_EVENTS); err != nil {
        panic(err)
    }
    defer sdl.Quit()

    myData := MyType{A: 42}

    // 加锁存入map
    mapLock.Lock()
    id := nextID
    dataMap[id] = myData
    nextID++
    mapLock.Unlock()

    ty := sdl.RegisterEvents(1)
    err := sdl.PushEvent(&sdl.UserEvent{
        Type:      ty,
        Timestamp: sdl.GetTicks(),
        Data1:     unsafe.Pointer(&id), // 传递ID的指针
    })
    if err != nil {
        panic(err)
    }

    // 模拟接收事件逻辑
    var event sdl.Event
    for event = sdl.WaitEvent(); event != nil; event = sdl.WaitEvent() {
        if ue, ok := event.(*sdl.UserEvent); ok && ue.Type == ty {
            receivedID := *(*uintptr)(ue.Data1)
            mapLock.Lock()
            data, exists := dataMap[receivedID]
            if exists {
                delete(dataMap, receivedID) // 取出后删除,避免内存泄漏
            }
            mapLock.Unlock()
            if exists {
                println("Received data:", data.A)
            }
            break
        }
    }
}

这种方法不用管理C内存,适合Go代码内部的事件传递,注意要保证map的并发安全,及时清理不再需要的数据。

方法3:使用Go通道传递数据

如果事件的发送和接收都在Go代码中,可以不用SDL的Data1字段传递数据,SDL事件仅作为通知信号,实际数据通过Go通道传递:

package main

import (
    "unsafe"

    "github.com/veandco/go-sdl2/sdl"
)

type MyType struct {
    A int
}

func main() {
    if err := sdl.Init(sdl.INIT_EVENTS); err != nil {
        panic(err)
    }
    defer sdl.Quit()

    dataChan := make(chan MyType, 1)
    myData := MyType{A: 42}

    ty := sdl.RegisterEvents(1)
    // 发送SDL事件作为信号
    err := sdl.PushEvent(&sdl.UserEvent{
        Type:      ty,
        Timestamp: sdl.GetTicks(),
    })
    if err != nil {
        panic(err)
    }
    // 同时将数据发送到通道
    dataChan <- myData

    // 模拟接收事件
    var event sdl.Event
    for event = sdl.WaitEvent(); event != nil; event = sdl.WaitEvent() {
        if ue, ok := event.(*sdl.UserEvent); ok && ue.Type == ty {
            // 从通道取出数据
            data := <-dataChan
            println("Received data:", data.A)
            break
        }
    }
}

这种方法完全贴合Go的并发模型,无需处理指针或全局状态,适合纯Go环境下的事件配合。


内容的提问来源于stack exchange,提问作者iBug

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 07:15:35