gocgo

How to copy *C.char?


What's the equivalent of mempcy() for *C.char?

I have a function that will be called from C using -buildmode=c-shared:

myGoStr := "blabla"

//export GetString
func GetString(text *C.char) {
 memcpy(text, myGoStr)
}

How can I copy the Go string to the C allocated memory behind *C.char?

I'm well aware of all the C.String functions (https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C) but I don't want Go to allocate the memory, I want to work directly with a C pointer given by the C application.

The only way I found is:

cStr := C.CString(text)
C.strcpy(text, cStr)
C.free(unsafe.Pointer(cStr))

but it's too overkill since it involves 2 copies (C.CString and C.strcpy)


Solution

  • There are multiple ways to achieve what you want but the thing is, either way will be unsafe — in one way or another. The reason for this is that memcpy is already unsafe (in the Go's frame of thinking) because its third argument — the number of bytes to copy — is not somehow obtained from the rest of that function's arguments (what the Go's copy does).

    So yes, you can avoid the usual Go's unsafe dance to construct a slice out of your *C.char to pass to copy and have that warm cozy feeling of being safe, but then another solution — such as just calling memcpy directly — will anyway rely on the said *C.char to point to a memory block with enough free space to contain at least as much bytes there is in the Go's source string, plus one, and nothing guarantees that.

    I mean, if you want to go this way, you can just do that:

    /*
    #include <string.h>
    
    static void memcpy_from_go(char *dst, _GoString_ src)
    {
        memcpy(dst, src.p, src.n);
    }
    */
    import "C"
    
    const myGoStr = "blabla"
    
    //export GetString
    func GetString(text *C.char) {
        C.memcpy_from_go(text, myGoStr)
        // (!) note that no NUL terminator is placed into the destination block
    }
    
    func main() {}
    

    But as there's no safeness anyway, I would argue that the analogous

    func strcpyToC(dst *C.char, src string) {
        n := len(src)
    
        ds := unsafe.Slice((*byte)(unsafe.Pointer(dst)), n+1)
    
        copy(ds, src)
        ds[n] = 0
    }
    
    //export GetString
    func GetString(text *C.char) {
        strcpyToC(text, myGoStr)
    }
    

    is just clearer and and just as unsafe.

    To reiterate in other words, you cannot do memory operations with the C side from your Go side and still claim your Go side is safe.