Search code examples
gomemory-managementcgo

Need help pinning go/c memory for ffi call


I am learning go and cgo and ffi, I have the header file below:

#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef struct MyStruct3 {
  const char *const *data;
  int length;
} MyStruct3;

void my_struct_3(const struct MyStruct3 *c_data);

And this go file

package main

/*
#cgo LDFLAGS: -L./release -lrust_ffi
#cgo CFLAGS: -I./target
#include <rust-ffi.h>
*/
import "C"
import (
    "fmt"
    "unsafe"
)

func main() {
    type MyStruct3 struct {
        data   **C.char
        length C.int
    }

    goStrings2 := []string{"Hello", "World", "from", "Go", "again"}

    pnr := new(runtime.Pinner)

    cStrings2 := make([]*C.char, len(goStrings2))
    for i, s := range goStrings2 {
        cString := C.CString(s)
        cStrings2[i] = cString
        // pnr.Pin(cString)
        defer C.free(unsafe.Pointer(cStrings2[i]))
    }

    cDataPtr := (**C.char)(unsafe.Pointer(&cStrings2[0]))
    // pnr.Pin(&goStrings2)
    pnr.Pin(&cDataPtr)
    // cLength := C.int(len(goStrings2))
    // pnr.Pin(&cLength)
    cStruct3 := MyStruct3{
        data:   cDataPtr,
        length: C.int(len(goStrings2)),
    }

    cStruct3prt := (*C.struct_MyStruct3)(unsafe.Pointer(&cStruct3))
    pnr.Pin(&cStruct3)
    C.my_struct_3(cStruct3prt)

    pnr.Unpin()

I am getting the following error

panic: runtime error: cgo argument has Go pointer to unpinned Go pointer

Go version

$ go version
go version go1.21.5 linux/amd64

Any idea on how to solve this issue?

thank you very much


Solution

  • Since the slice is allocated by the Go runtime, you have to pin it:

    func main() {
        goStrings2 := []string{"Hello", "World", "from", "Go", "again"}
    
        // Convert Go slice to a C array of C strings
        cStrings2 := make([]*C.char, len(goStrings2))
        for i, s := range goStrings2 {
            cStrings2[i] = C.CString(s)
            defer C.free(unsafe.Pointer(cStrings2[i]))
        }
    
        var pin runtime.Pinner
        defer pin.Unpin()
    
        cData := &cStrings2[0]
        pin.Pin(cData)
        C.my_struct_3(&C.MyStruct3{
            data:   cData,
            length: C.int(len(goStrings2)),
        })
    }