Go 與 C 溝通時,指標與生命週期是第一原則。這篇以 Mongory 的 Go Bridge 為例,整理出在實作中踩過、避過的雷,並給出可直接對照的實作片段
對應工具函式(節錄):
void *go_handle_to_ptr(uintptr_t h) { return (void *)h; }
uintptr_t go_ptr_to_handle(void *p) { return (uintptr_t)p; }
...
func handleToPtr(h rcgo.Handle) unsafe.Pointer {
return C.go_handle_to_ptr(C.uintptr_t(uintptr(h)))
}
func ptrToHandle(ptr unsafe.Pointer) rcgo.Handle {
return rcgo.Handle(C.go_ptr_to_handle(ptr))
}
unsafe.Pointer
傳進 C,日後 C 回傳再還原為 Handle 取回 Go 值在 Mongory 中,所有 Handle 都掛在記憶體池(MemoryPool)上,跟著 Reset/Free
一起回收:
type MemoryPool struct {
CPoint *C.mongory_memory_pool
handles []rcgo.Handle
}
...
func (m *MemoryPool) trackHandle(h rcgo.Handle) {
m.handles = append(m.handles, h)
}
func (m *MemoryPool) Reset() {
C.go_mongory_memory_pool_reset(m.CPoint)
for _, h := range m.handles { h.Delete() }
m.handles = m.handles[:0]
}
以建立 Matcher 時傳入「context」為例:
h := rcgo.NewHandle(context)
pool.trackHandle(h)
cpoint := C.mongory_matcher_new(pool.CPoint, conditionValue.CPoint, handleToPtr(h))
handleToPtr(h)
(token),不直接傳 Go 指標Shallow Array 的 get
:
pool := MemoryPool{CPoint: a.base.pool}
h := ptrToHandle(a.go_array)
target := h.Value()
rv := reflect.ValueOf(target)
var iv any
if rv.IsValid() && (rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array) {
iv = rv.Index(int(index)).Interface()
}
return pool.ValueConvert(iv).CPoint
ptrToHandle
還原 Go 值,短生命週期使用Shallow Table 的 get
同理:
h := ptrToHandle(a.go_table)
target := h.Value()
rv := reflect.ValueOf(target)
var iv any
if rv.IsValid() && rv.Kind() == reflect.Map {
v := rv.MapIndex(reflect.ValueOf(C.GoString(key)))
if v.IsValid() { iv = v.Interface() }
}
return pool.ValueConvert(iv).CPoint
pool.Reset/Free
一起清void *
保存 token 已足夠,取用時再還原Match
,避免重複建構unsafe.Pointer
/cgo.Handle
——這些細節已封裝在 API 內在 Go 與 C 的邊界上,正確姿勢是:以 Handle 作為唯一的跨界「代幣」,以 pool 管理生命週期,所有指標都以 unsafe.Pointer
作短暫通道。這能兼顧安全、可維運與效能,讓讀者專注在條件與資料本身