Search code examples
gojnacgo

How do I pass **C.char data back to caller's buffer?


I am going to build a Go shared object binary (.DLL and .so) that passes back a string to Java. To work out the C string passing from Go I wrote this:

package main

/*
#include <stdlib.h>
*/
import "C"
import (
    "log"
    "unsafe"
)

//export passBackHello
func passBackHello(buf **C.char) C.int {
    str := "Hello World!"
    length := len(str)
    cString := C.CString(str) // returns *C.char
    defer C.free(unsafe.Pointer(cString))
    log.Println("In passBackHello: cString:", C.GoStringN(cString, C.int(length)))
    *buf = C.CString(str) // works
    *buf = cString        // doesn't work
    log.Println("In passBackHello: buf:", C.GoStringN(*buf, C.int(length)))

    return C.int(length)
}

func main() {
    buf := make([]byte, 8192) //create my buffer
    cStrPointer := (**C.char)(unsafe.Pointer(&buf[0]))
    defer C.free(unsafe.Pointer(cStrPointer))
    lengthCint := passBackHello(cStrPointer)
    log.Println("In main: length:", int(lengthCint))
    log.Println("In main: buf:", C.GoStringN(*cStrPointer, lengthCint))
    log.Println("In main: buf:", C.GoString(*cStrPointer))
}

When I use *buf = C.CString(str) in function passBackHello it works:

2018/03/31 19:33:54 In passBackHello: cString: Hello World!
2018/03/31 19:33:54 In passBackHello: buf: Hello World!
2018/03/31 19:33:54 In main: length: 12
2018/03/31 19:33:54 In main: buf: Hello World!
2018/03/31 19:33:54 In main: buf: Hello World!
exit status 3221226356

When I use *buf = cStringPointer in function passBackHello it shows in buf while in passBackHello but not in main:

2018/03/31 19:33:05 In passBackHello: cString: Hello World!
2018/03/31 19:33:05 In passBackHello: buf: Hello World!
2018/03/31 19:33:05 In main: length: 12
2018/03/31 19:33:05 In main: buf:  ⌂3     X☺�
2018/03/31 19:33:05 In main: buf:  ⌂3
exit status 3221226356

I need to get the *buf = cStringPointer version to work because that one has the C.free of the C string variable. I am running go version go1.10.1 windows/amd64.

UPDATE

After applying Azeem's answer and some other cleanup the working Go code looks like this:

//export passBackHello
func passBackHello(cStrPointer **C.char) C.int {
    str := "Hello World!"
    length := len(str)
    *cStrPointer = C.CString(str) // copies *C.char into caller's buffer
    log.Println("In passBackHello *cStrPointer:", C.GoStringN(*cStrPointer, C.int(length)))
    return C.int(length)
}

func main() {
    buf := make([]byte, 8192) //create my buffer
    cStrPointer := (**C.char)(unsafe.Pointer(&buf[0]))
    defer C.free(unsafe.Pointer(cStrPointer))
    lengthCint := passBackHello(cStrPointer)
    log.Println("In main: length:", int(lengthCint))
    log.Println("In main: *cStrPointer:", C.GoStringN(*cStrPointer, lengthCint))
    log.Println("In main: *cStrPointer:", C.GoString(*cStrPointer))
}

And just to be complete the Java caller looks like this:

// allocate a void**
final PointerByReference decCStringPointer = new PointerByReference();
// call the C function
Integer decLength = gpg.passBackHello(decCStringPointer);
// extract the void* that was allocated in C
final Pointer p = decCStringPointer.getValue();
// extract the null-terminated string from the Pointer
final String decValue = p.getString(0);
System.out.printf("decrypted length: %d\n", decLength);
System.out.printf("decrypted value: %s\n", decValue);

It looks like the buffer is in C memory. How do I free it from Java?


Solution

  • According to the documentation of C.CString function:

    // Go string to C string
    // The C string is allocated in the C heap using malloc.
    // It is the caller's responsibility to arrange for it to be
    // freed, such as by calling C.free (be sure to include stdlib.h
    // if C.free is needed).
    func C.CString(string) *C.char
    

    You don't need to free it in your function. It is the caller (i.e. main function in your case) that is supposed to free it.

    Remove the line defer C.free(unsafe.Pointer(cString)) from your passBackHello and it should work!

    NOTE:
    You don't need that buffer (i.e. buf) because it's the pointer that is being returned and the memory is already allocated. You may copy it in your buffer if required and free the pointer.