1

Can I convert an array of strings (char**) returned from a C (cgo) function in Go? The code below compiles and runs, but I'm unable to range through a list of strings.
And I'm not even sure if it breaks the rules on "passing pointers": https://golang.org/cmd/cgo/ Any thoughts would be helpful, it's been years since I coded in C! Thanks in advance!

package main
/*
#include "stdlib.h"

char** getlist ()
{
    char **array = NULL;
    array = (char**)realloc(array, 2*sizeof(*array));
    array[0]="HELLO";
    array[1]="WORLD";
    return array;
}

*/
import "C"

import (
    "log"
    "unsafe"
)

func main() {
    list := C.getlist();
    log.Printf("\n========\n C.getList()=%s", list)
    ulist := unsafe.Pointer(list)
    log.Printf("\n========\nulist=%s", ulist)
}
user3757849
  • 199
  • 2
  • 14
  • 1
    See the wiki page on cgo for example: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices – JimB May 26 '20 at 01:23
  • Thanks for the comment, but the wiki page's example is for a 1D array. How would I code that conversion for a 2D array? I'm returning a C.char** not a C.char*. – user3757849 May 26 '20 at 19:15
  • The outer array is still "1D", the inner type doesn't change anything. I'll put an example – JimB May 26 '20 at 19:44

2 Answers2

2

In order to iterate over the strings in Go, you need to convert the array to a Go slice. We can skip allocation here, and convert it directly (your example statically sets the length to 2, but in practice you would likely have another source for this size)

cSlice := (*[1 << 28]*C.char)(unsafe.Pointer(list))[:2:2]

We can iterate over this directly, and use the C.GoString function to convert the C strings. This is safer to store since it's copying the data to Go memory, but if this slice were exceptionally large we could save the allocation with the same unsafe conversion as above, though you would first need to find the length of each string.

var slice []string
for _, s := range (*[1 << 28]*C.char)(unsafe.Pointer(list))[:2:2] {
    slice = append(slice, C.GoString(s))
}
JimB
  • 104,193
  • 13
  • 262
  • 255
  • awsome - thank you! Is there any way to get the length of the array? I changed the 2 to a larger number and If I check for nil on s and break the loop: if s == nil { break } I can get the length as the third element is nil, but I noticed the 4th element is not nil. Am I basically trampling on unknown memory locations by looping through a slice like: (unsafe.Pointer(list))[:2000:2000]? – user3757849 May 26 '20 at 20:14
  • @user3757849: You either need to get the array size out of band (most C functions will return the size of an array, along with the array itself), or you need to know what the final sentinel value is to look for, a null in the case of C strings. Unfortunately there's mo way to avoid needing to know how C works to use cgo. You can check for `nil`, as long as you can be certain that an extra null value was allocated for the final array element. – JimB May 26 '20 at 21:14
  • hi jimb thank you. Yes, sticking in a null value at the end is a great idea. I see your point about understanding C! I might get brave and try to return the length of the array via a pointer. The C is slowly starting to come back to me. – user3757849 May 26 '20 at 21:36
  • Anyone knows where do 28 (or 30) come from? – CheatEx Jul 20 '23 at 20:55
  • 1
    @CheatEx: read https://stackoverflow.com/questions/48756732/what-does-1-30c-yourtype-do-exactly-in-cgo/48756785#48756785 – JimB Jul 20 '23 at 20:59
1

After a couple hours of browsing, I found this concise solution (source):

list := C.getList()
length := 2
slice := make([]string, length)
for _, v := range unsafe.Slice(list, length) {
    slice = append(slice, C.GoString(v))
}

For your length guessing issue, you can just make your C function take a pointer to a string array and return the size. It would then look like this:

var list **C.char 
length := C.getList(&list)
slice := make([]string, length)
for _, v := range unsafe.Slice(list, length) {
    slice = append(slice, C.GoString(v))
}
Roj
  • 995
  • 1
  • 8
  • 22