Go runtime/cgo 包详解
概述
runtime/cgo 包为 cgo 工具生成的代码提供运行时支持。它主要用于在 Go 和 C 之间安全地传递包含 Go 指针的值,而不违反 cgo 指针传递规则。
重要说明:
- 此包主要用于 cgo 生成的代码
- 提供了 Handle 机制来安全传递 Go 值给 C
- 解决了 C 代码需要引用 Go 值的场景
- 大多数 Go 程序员不需要直接使用此包
cgo 指针传递规则:
- Go 代码可以传递不包含 Go 指针的值给 C
- 包含 Go 指针的值不能直接传递给 C
- Handle 提供了一种安全的方式来绕过这个限制
包导入
import "runtime/cgo"
基本使用
示例 1:使用 Handle 传递 Go 字符串给 C
package main
/*
#include <stdint.h>
extern void MyGoPrint(uintptr_t handle);
void myprint(uintptr_t handle);
*/
import "C"
import "runtime/cgo"
//export MyGoPrint
func MyGoPrint(handle C.uintptr_t) {
h := cgo.Handle(handle)
val := h.Value().(string)
println(val)
h.Delete()
}
func main() {
val := "hello Go"
C.myprint(C.uintptr_t(cgo.NewHandle(val)))
}
C 代码部分:
#include <stdint.h>
// Go 函数声明
extern void MyGoPrint(uintptr_t handle);
// C 函数实现
void myprint(uintptr_t handle) {
MyGoPrint(handle);
}
运行结果:
hello Go
示例 2:使用 Handle 传递任意 Go 值
package main
/*
#include <stdint.h>
extern void ProcessData(uintptr_t handle);
*/
import "C"
import (
"fmt"
"runtime/cgo"
)
type Data struct {
Name string
Age int
}
//export ProcessData
func ProcessData(handle C.uintptr_t) {
h := cgo.Handle(handle)
data := h.Value().(*Data)
fmt.Printf("Name: %s, Age: %d\n", data.Name, data.Age)
h.Delete()
}
func main() {
data := &Data{Name: "Alice", Age: 30}
C.ProcessData(C.uintptr_t(cgo.NewHandle(data)))
}
运行结果:
Name: Alice, Age: 30
类型详解
Handle
Handle 提供了一种方式来在 Go 和 C 之间传递包含 Go 指针的值,而不违反 cgo 指针传递规则。
type Handle uintptr
特性:
- Handle 是一个整数值,可以表示任何 Go 值
- Handle 可以传递给 C,然后再传回 Go
- Go 代码可以使用 Handle 检索原始 Go 值
- Handle 的底层类型保证能容纳任何指针的位模式
- Handle 的零值无效,可用作 C API 中的哨兵值
重要说明:
- Handle 使用资源,程序必须在不需时显式调用 Delete
- 假设 C 代码可能会持有 handle,因此必须显式删除
- 无效的 Handle 会导致 Value() 和 Delete() panic
NewHandle
func NewHandle(v interface{}) Handle
说明:为给定值返回一个 handle。该 handle 在程序调用 Delete 之前一直有效。
使用示例:
package main
import (
"fmt"
"runtime/cgo"
)
func main() {
// 创建 handle
val := "test value"
h := cgo.NewHandle(val)
fmt.Printf("Handle created: %v\n", h)
// 获取值
retrieved := h.Value()
fmt.Printf("Retrieved: %v\n", retrieved)
// 删除 handle
h.Delete()
fmt.Println("Handle deleted")
}
运行结果:
Handle created: 1
Retrieved: test value
Handle deleted
典型用法:
package main
/*
#include <stdint.h>
extern void Callback(uintptr_t handle);
void doWork(uintptr_t handle);
*/
import "C"
import (
"fmt"
"runtime/cgo"
"unsafe"
)
//export Callback
func Callback(handle C.uintptr_t) {
h := cgo.Handle(handle)
data := h.Value().(map[string]int)
fmt.Printf("Callback received: %v\n", data)
h.Delete()
}
func doWork(data map[string]int) {
h := cgo.NewHandle(data)
defer h.Delete()
// 传递给 C 代码
C.doWork(C.uintptr_t(h))
}
func main() {
data := map[string]int{"a": 1, "b": 2}
doWork(data)
}
Value
func (h Handle) Value() interface{}
说明:返回有效 handle 关联的 Go 值。如果 handle 无效会 panic。
使用示例:
package main
import (
"fmt"
"runtime/cgo"
)
func main() {
// 创建不同类型的 handle
handles := []cgo.Handle{
cgo.NewHandle("string"),
cgo.NewHandle(42),
cgo.NewHandle([]int{1, 2, 3}),
cgo.NewHandle(map[string]int{"a": 1}),
}
for _, h := range handles {
val := h.Value()
fmt.Printf("Type: %T, Value: %v\n", val, val)
h.Delete()
}
}
运行结果:
Type: string, Value: string
Type: int, Value: 42
Type: []int, Value: [1 2 3]
Type: map[string]int, Value: map[a:1]
错误处理:
package main
import (
"fmt"
"runtime/cgo"
)
func safeValue(h cgo.Handle) (interface{}, error) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
// 删除后的 handle 会 panic
return h.Value(), nil
}
func main() {
h := cgo.NewHandle("test")
h.Delete()
_, err := safeValue(h)
if err != nil {
fmt.Println("Error:", err)
}
}
运行结果:
Recovered from panic: invalid handle
Delete
func (h Handle) Delete()
说明:使 handle 无效。应该在程序不再需要将 handle 传递给 C 且 C 代码不再持有 handle 值时调用。如果 handle 无效会 panic。
使用示例:
package main
import (
"fmt"
"runtime/cgo"
)
func main() {
h := cgo.NewHandle("test")
// 使用 handle
val := h.Value()
fmt.Println("Value:", val)
// 删除 handle
h.Delete()
fmt.Println("Handle deleted")
// 再次删除会 panic
defer func() {
if r := recover(); r != nil {
fmt.Println("Panic:", r)
}
}()
h.Delete() // panic
}
运行结果:
Value: test
Handle deleted
Panic: invalid handle
资源管理最佳实践:
package main
/*
#include <stdint.h>
extern void Process(uintptr_t handle);
*/
import "C"
import (
"runtime/cgo"
)
//export Process
func Process(handle C.uintptr_t) {
h := cgo.Handle(handle)
data := h.Value().([]byte)
// 处理数据...
_ = data
h.Delete()
}
func processData(data []byte) {
h := cgo.NewHandle(data)
defer h.Delete() // 确保删除
C.Process(C.uintptr_t(h))
}
func main() {
data := []byte("hello")
processData(data)
}
Incomplete
type Incomplete struct{}
说明:Incomplete 专门用于不完整 C 类型的语义。
使用示例:
package main
/*
struct IncompleteType; // 不完整的 C 类型
*/
import "C"
import "runtime/cgo"
// Incomplete 用于表示不完整的 C 结构体类型
var _ cgo.Incomplete
func main() {
// 通常不直接使用
// 主要用于 cgo 生成的代码中
}
典型示例
示例 1:C 回调中使用 Handle
package main
/*
#include <stdint.h>
typedef void (*CallbackFunc)(uintptr_t handle, int result);
extern void GoCallback(uintptr_t handle, int result);
static inline void registerCallback(CallbackFunc cb, uintptr_t handle) {
cb(handle, 42);
}
*/
import "C"
import (
"fmt"
"runtime/cgo"
)
//export GoCallback
func GoCallback(handle C.uintptr_t, result C.int) {
h := cgo.Handle(handle)
callback := h.Value().(func(int))
callback(int(result))
h.Delete()
}
func registerCallback(callback func(int)) {
h := cgo.NewHandle(callback)
C.registerCallback(C.CallbackFunc(C.GoCallback), C.uintptr_t(h))
}
func main() {
registerCallback(func(result int) {
fmt.Printf("Callback called with result: %d\n", result)
})
}
运行结果:
Callback called with result: 42
示例 2:传递结构体指针
package main
/*
#include <stdint.h>
extern void ProcessStruct(uintptr_t handle);
*/
import "C"
import (
"fmt"
"runtime/cgo"
)
type Config struct {
Name string
Timeout int
Debug bool
}
//export ProcessStruct
func ProcessStruct(handle C.uintptr_t) {
h := cgo.Handle(handle)
config := h.Value().(*Config)
fmt.Printf("Config: %+v\n", config)
fmt.Printf("Name: %s, Timeout: %d, Debug: %v\n",
config.Name, config.Timeout, config.Debug)
h.Delete()
}
func main() {
config := &Config{
Name: "myapp",
Timeout: 30,
Debug: true,
}
C.ProcessStruct(C.uintptr_t(cgo.NewHandle(config)))
}
运行结果:
Config: &{myapp 30 true}
Name: myapp, Timeout: 30, Debug: true
示例 3:传递切片
package main
/*
#include <stdint.h>
extern void ProcessSlice(uintptr_t handle);
*/
import "C"
import (
"fmt"
"runtime/cgo"
)
//export ProcessSlice
func ProcessSlice(handle C.uintptr_t) {
h := cgo.Handle(handle)
data := h.Value().([]int)
sum := 0
for _, v := range data {
sum += v
}
fmt.Printf("Sum: %d\n", sum)
h.Delete()
}
func main() {
data := []int{1, 2, 3, 4, 5}
C.ProcessSlice(C.uintptr_t(cgo.NewHandle(data)))
}
运行结果:
Sum: 15
示例 4:传递通道
package main
/*
#include <stdint.h>
extern void SendToChannel(uintptr_t handle, int value);
*/
import "C"
import (
"fmt"
"runtime/cgo"
)
//export SendToChannel
func SendToChannel(handle C.uintptr_t, value C.int) {
h := cgo.Handle(handle)
ch := h.Value().(chan int)
ch <- int(value)
h.Delete()
}
func main() {
ch := make(chan int)
go func() {
C.SendToChannel(C.uintptr_t(cgo.NewHandle(ch)), 100)
}()
result := <-ch
fmt.Printf("Received: %d\n", result)
}
运行结果:
Received: 100
示例 5:传递函数
package main
/*
#include <stdint.h>
extern void ExecuteCallback(uintptr_t handle);
*/
import "C"
import (
"fmt"
"runtime/cgo"
)
//export ExecuteCallback
func ExecuteCallback(handle C.uintptr_t) {
h := cgo.Handle(handle)
fn := h.Value().(func() string)
result := fn()
fmt.Println("Function result:", result)
h.Delete()
}
func main() {
callback := func() string {
return "Hello from Go function!"
}
C.ExecuteCallback(C.uintptr_t(cgo.NewHandle(callback)))
}
运行结果:
Function result: Hello from Go function!
示例 6:多个 Handle 管理
package main
/*
#include <stdint.h>
extern void ProcessMultiple(uintptr_t h1, uintptr_t h2);
*/
import "C"
import (
"fmt"
"runtime/cgo"
)
//export ProcessMultiple
func ProcessMultiple(h1, h2 C.uintptr_t) {
handle1 := cgo.Handle(h1)
handle2 := cgo.Handle(h2)
str := handle1.Value().(string)
num := handle2.Value().(int)
fmt.Printf("String: %s, Number: %d\n", str, num)
handle1.Delete()
handle2.Delete()
}
func main() {
h1 := cgo.NewHandle("test")
h2 := cgo.NewHandle(42)
C.ProcessMultiple(C.uintptr_t(h1), C.uintptr_t(h2))
}
运行结果:
String: test, Number: 42
示例 7:Handle 与 unsafe.Pointer 配合
package main
/*
extern void ProcessContext(void *context);
*/
import "C"
import (
"fmt"
"runtime/cgo"
"unsafe"
)
//export ProcessContext
func ProcessContext(context unsafe.Pointer) {
h := *(*cgo.Handle)(context)
data := h.Value().(map[string]string)
for k, v := range data {
fmt.Printf("%s: %s\n", k, v)
}
h.Delete()
}
func main() {
data := map[string]string{
"name": "Alice",
"email": "alice@example.com",
}
h := cgo.NewHandle(data)
C.ProcessContext(unsafe.Pointer(&h))
}
运行结果:
name: Alice
email: alice@example.com
示例 8:错误处理和验证
package main
import (
"fmt"
"runtime/cgo"
)
type SafeHandle struct {
handle cgo.Handle
valid bool
}
func NewSafeHandle(v interface{}) *SafeHandle {
return &SafeHandle{
handle: cgo.NewHandle(v),
valid: true,
}
}
func (sh *SafeHandle) Value() (interface{}, error) {
if !sh.valid {
return nil, fmt.Errorf("handle is invalid")
}
return sh.handle.Value(), nil
}
func (sh *SafeHandle) Delete() {
if sh.valid {
sh.handle.Delete()
sh.valid = false
}
}
func main() {
sh := NewSafeHandle("test value")
val, err := sh.Value()
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Value:", val)
}
sh.Delete()
fmt.Println("Handle deleted")
// 再次获取值会返回错误
_, err = sh.Value()
if err != nil {
fmt.Println("Error:", err)
}
}
运行结果:
Value: test value
Handle deleted
Error: handle is invalid
最佳实践
1. 始终删除 Handle
// ✅ 推荐:使用 defer
func process(data interface{}) {
h := cgo.NewHandle(data)
defer h.Delete()
C.someFunction(C.uintptr_t(h))
}
// ❌ 不推荐:可能忘记删除
func process(data interface{}) {
h := cgo.NewHandle(data)
C.someFunction(C.uintptr_t(h))
// 忘记删除会导致资源泄漏
}
2. 避免重复删除
// ✅ 推荐:确保只删除一次
func process(data interface{}) {
h := cgo.NewHandle(data)
defer h.Delete()
C.someFunction(C.uintptr_t(h))
}
// ❌ 不推荐:可能删除多次
func process(data interface{}) {
h := cgo.NewHandle(data)
h.Delete()
// ... 可能再次删除
}
3. 验证 Handle 有效性
// ✅ 推荐:添加验证
func safeValue(h cgo.Handle) (interface{}, error) {
defer func() {
if r := recover(); r != nil {
// 处理 panic
}
}()
return h.Value(), nil
}
4. 不要在 C 代码中保留 Handle 副本
// ✅ 推荐:C 代码不保留副本
/*
void process(uintptr_t handle) {
use(handle); // 立即使用
}
*/
// ❌ 不推荐:C 代码保留副本
/*
uintptr_t global_handle; // 危险!
void process(uintptr_t handle) {
global_handle = handle; // 保留副本
}
*/
与其他包配合
runtime.Pinner
package main
/*
#include <stdint.h>
extern void Process(uintptr_t handle);
*/
import "C"
import (
"runtime"
"runtime/cgo"
)
//export Process
func Process(handle C.uintptr_t) {
h := cgo.Handle(handle)
data := h.Value().(*[]byte)
// 使用数据...
_ = data
}
func main() {
data := make([]byte, 100)
// 固定内存
var pinner runtime.Pinner
pinner.Pin(&data)
h := cgo.NewHandle(&data)
defer h.Delete()
defer pinner.Unpin()
C.Process(C.uintptr_t(h))
}
unsafe 包
package main
/*
extern void Process(void *ptr);
*/
import "C"
import (
"runtime/cgo"
"unsafe"
)
//export Process
func Process(ptr unsafe.Pointer) {
h := *(*cgo.Handle)(ptr)
val := h.Value().(string)
println(val)
h.Delete()
}
func main() {
val := "test"
h := cgo.NewHandle(val)
C.Process(unsafe.Pointer(&h))
}
快速参考
类型
| 类型 | 说明 |
|---|---|
| Handle | 用于在 Go 和 C 之间安全传递 Go 值 |
| Incomplete | 用于不完整 C 类型的语义 |
Handle 方法
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
| NewHandle | v interface{} | Handle | 创建 handle |
| Value | - | interface{} | 获取关联的 Go 值 |
| Delete | - | - | 删除 handle |
注意事项
1. Handle 资源管理
- Handle 使用资源,必须显式删除
- 假设 C 代码可能持有 handle,因此必须显式删除
- 未删除的 handle 会导致资源泄漏
2. Handle 有效性
- Handle 的零值无效
- 删除后的 handle 无效
- 对无效 handle 调用 Value() 或 Delete() 会 panic
3. C 代码限制
- C 代码不应保留 handle 的副本
- 除非内存被显式固定(使用 runtime.Pinner)
- C 代码必须在使用后立即将 handle 传回 Go
4. 类型安全
- Handle 可以持有任意 Go 值
- 检索时需要类型断言
- 错误的类型断言会 panic
5. 性能考虑
- Handle 操作有少量开销
- 频繁创建和删除 handle 可能影响性能
- 尽可能复用 handle
6. 并发安全
- Handle 本身不是并发安全的
- 多个 goroutine 不应同时操作同一个 handle
- 每个 goroutine 应使用自己的 handle
总结
runtime/cgo 包提供了在 Go 和 C 之间安全传递 Go 值的机制。
核心要点:
- Handle 用于安全传递包含 Go 指针的值给 C
- 必须显式调用 Delete() 删除 handle
- Handle 可以传递任意 Go 值(字符串、切片、映射、通道、函数等)
- C 代码不应保留 handle 副本
- 无效的 handle 会导致 panic
主要用途:
- cgo 生成的代码
- 需要在 Go 和 C 之间传递复杂 Go 值的场景
- C 回调需要访问 Go 数据的场景
- 实现 Go 和 C 之间的双向通信
重要提醒:
- 这是低级包,大多数 Go 程序员不需要直接使用
- 使用 cgo 命令生成的代码会自动处理 handle
- 手动使用时必须小心管理资源