使用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
相关产品推荐
相关产品推荐

