Golang中如何解决runtime error: cgo参数包含Go指针指向Go指针的问题

Golang中如何解决runtime error: cgo参数包含Go指针指向Go指针的问题 我想将一个 Go 指针通过 C 传递回 Go 回调函数……并收到上述错误。如果我通过以下方式禁用 cgo 检查器:

export GODEBUG=cgocheck=0

一切运行正常;

详情:我想将 argv 变量 []interface{} 通过 C 传递回 Go

// 问题:[ErrorCatch] runtime error: cgo argument has Go pointer to Go pointer
// 解决:export GODEBUG=cgocheck=0
func (this *MqC) Send (callSig string, argv ...interface{}) {
  hdl := this.getCTX()
  callSig_ptr := (C.MQ_CST)(C.CString(callSig))
  defer C.free((unsafe.Pointer)(callSig_ptr))

  var errVal C.enum_MqErrorE = C.gomsgque_Send(hdl, callSig_ptr, C.MQ_INT(len(argv)), unsafe.Pointer(&argv))

  if (errVal > C.MQ_CONTINUE) { MqErrorC_Check(C.MQ_MNG(hdl), errVal) }
}

回调函数是

//export atomSet
func atomSet( hdl   C.MQ_MNG, objv  unsafe.Pointer, skip  C.MQ_INT, typ C.enum_MqSendE,
                inP  unsafe.Pointer) C.enum_MqErrorE {
  ifc := (*(*[]interface{})(objv))[skip]
...
}

现在我希望能有一个对 Go 更友好的解决方案。 谢谢。


更多关于Golang中如何解决runtime error: cgo参数包含Go指针指向Go指针的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

我不了解Go的内部工作原理,但这里有一个猜测

根据对callSig_ptr字符串指针的延迟释放,在我看来callSigargv都不应该在调用(*MqC).Send之后继续存在,但Go编译器并不知道这一点。它只是看到你将一个局部参数(argv)的地址传递给了C,并且这个值可能需要在调用gomsgque_Send之后继续存活。

如果gomsgque_Send在“C世界”的某个地方存储了对&argv的引用,然后(*MqC).Send返回,接着发生了一次垃圾回收(清理了在调用(*MqC).Send时逃逸到堆上的argv),那么当最终调用atomSet时,它会因为argv已被清理而因段错误而崩溃。

如果callSigargv的生命周期不需要在gomsgque_Send返回后继续存在,可以尝试在调用gomsgque_Send之外获取地址,像这样:

func (this *MqC) Send (callSig string, argv ...interface{}) {
  hdl := this.getCTX()
  callSig_ptr := (C.MQ_CST)(C.CString(callSig))
  defer C.free((unsafe.Pointer)(callSig_ptr))

  argv_ptr := unsafe.Pointer(&argv)

  var errVal C.enum_MqErrorE = C.gomsgque_Send(hdl, callSig_ptr, C.MQ_INT(len(argv)), argv_ptr)

  if (errVal > C.MQ_CONTINUE) { MqErrorC_Check(C.MQ_MNG(hdl), errVal) }
}

我不确定这一定能行,但可以试试看。

更多关于Golang中如何解决runtime error: cgo参数包含Go指针指向Go指针的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个典型的cgo指针传递问题。根据Go 1.17+的cgo规则,Go指针不能包含指向其他Go指针的指针。以下是几种解决方案:

方案1:使用uintptr作为中间层(推荐)

func (this *MqC) Send(callSig string, argv ...interface{}) {
    hdl := this.getCTX()
    callSig_ptr := (C.MQ_CST)(C.CString(callSig))
    defer C.free(unsafe.Pointer(callSig_ptr))
    
    // 将slice转换为uintptr传递
    argvPtr := uintptr(unsafe.Pointer(&argv))
    
    var errVal C.enum_MqErrorE = C.gomsgque_Send(
        hdl, 
        callSig_ptr, 
        C.MQ_INT(len(argv)), 
        unsafe.Pointer(&argvPtr))  // 传递uintptr的指针
    
    if errVal > C.MQ_CONTINUE { 
        MqErrorC_Check(C.MQ_MNG(hdl), errVal) 
    }
}

回调函数中:

//export atomSet
func atomSet(hdl C.MQ_MNG, objv unsafe.Pointer, skip C.MQ_INT, 
             typ C.enum_MqSendE, inP unsafe.Pointer) C.enum_MqErrorE {
    
    // 从uintptr恢复原始指针
    argvPtr := *(*uintptr)(objv)
    argv := *(*[]interface{})(unsafe.Pointer(argvPtr))
    
    ifc := argv[skip]
    // ... 处理ifc
}

方案2:使用全局映射表

var (
    argvMap   = make(map[uintptr][]interface{})
    argvMutex sync.RWMutex
    nextID    uintptr = 1
)

func (this *MqC) Send(callSig string, argv ...interface{}) {
    hdl := this.getCTx()
    callSig_ptr := (C.MQ_CST)(C.CString(callSig))
    defer C.free(unsafe.Pointer(callSig_ptr))
    
    // 在全局映射中存储slice
    argvMutex.Lock()
    id := nextID
    argvMap[id] = argv
    nextID++
    argvMutex.Unlock()
    
    // 传递ID而不是指针
    idPtr := unsafe.Pointer(&id)
    
    var errVal C.enum_MqErrorE = C.gomsgque_Send(
        hdl, 
        callSig_ptr, 
        C.MQ_INT(len(argv)), 
        idPtr)
    
    // 清理映射(根据实际需求调整清理时机)
    defer func() {
        argvMutex.Lock()
        delete(argvMap, id)
        argvMutex.Unlock()
    }()
    
    if errVal > C.MQ_CONTINUE { 
        MqErrorC_Check(C.MQ_MNG(hdl), errVal) 
    }
}

回调函数:

//export atomSet
func atomSet(hdl C.MQ_MNG, objv unsafe.Pointer, skip C.MQ_INT,
             typ C.enum_MqSendE, inP unsafe.Pointer) C.enum_MqErrorE {
    
    // 从ID获取原始slice
    id := *(*uintptr)(objv)
    
    argvMutex.RLock()
    argv, exists := argvMap[id]
    argvMutex.RUnlock()
    
    if !exists {
        // 处理错误
    }
    
    ifc := argv[skip]
    // ... 处理ifc
}

方案3:使用C内存分配

func (this *MqC) Send(callSig string, argv ...interface{}) {
    hdl := this.getCTX()
    callSig_ptr := (C.MQ_CST)(C.CString(callSig))
    defer C.free(unsafe.Pointer(callSig_ptr))
    
    // 在C堆上分配内存
    size := C.size_t(unsafe.Sizeof(uintptr(0)) * uintptr(len(argv)))
    cArray := C.malloc(size)
    defer C.free(cArray)
    
    // 将interface{}转换为uintptr存储到C内存
    slice := (*[1 << 30]uintptr)(cArray)
    for i, v := range argv {
        // 注意:这里假设interface{}可以直接转换为uintptr
        // 实际可能需要更复杂的处理
        slice[i] = *(*uintptr)(unsafe.Pointer(&v))
    }
    
    var errVal C.enum_MqErrorE = C.gomsgque_Send(
        hdl, 
        callSig_ptr, 
        C.MQ_INT(len(argv)), 
        cArray)
    
    if errVal > C.MQ_CONTINUE { 
        MqErrorC_Check(C.MQ_MNG(hdl), errVal) 
    }
}

方案1是最直接的解决方案,它利用了uintptr作为不透明指针的桥梁,避免了cgo的指针检查。

回到顶部