Search code examples
arraysgomultidimensional-arraycgo

Access Go's array of arrays from C


Clarification: There does not seem to be a documented example of sending a GO map to a C function as a multidimentional array. This question is intended to find a common solution for anyone sending a collection of data from GO to C.

Question: I have a map[string]string in Go, I'm looking to iterate over this key/value pair in C++.

In GO I have the following to create a multidimensional array

    var argv = make([][]*C.char, len(keypairs))
    count := 0
    for key, val := range keypairs {
        var argv2 = make([]*C.char, 2)
        csKey := C.CString(key)
        csVal := C.CString(val)

        argv2[0] = csKey
        argv2[1] = csVal
        argv[count] = argv2
        count++

        C.free(unsafe.Pointer(csKey))
        C.free(unsafe.Pointer(csVal))
    }
    C.writeKeyValuePairs(&argv[0])

Then in C++ I have:

bool writeKeyValuePairs(char * pairs[][2]) {
     ...
}

The result is: cannot use _cgo1 (type *[]*_Ctype_char) as type *[2]*_Ctype_char in argument


Update to @kostix: Here's where it's at now:

    var argv = make([][2]*C.char, len(tags))
    i := 0
    for key, val := range tags {
        var argv2 = new([2]*C.char)
        argv2[0], argv2[1] = C.CString(key), C.CString(val)
        argv[i] = argv2
        i++
    }
    C.writeKeyValuePairs(&argv[0])

The error being: Cannot use 'argv2' (type *[2]*C.char) as type [2]*C.char


Solution

  • In Go, []T is not what you think it is: it is not an array but rather a slice.

    A slice is a struct of three fields: length int, capacity int and a pointer to the first element of the underlying (backing) array.

    Conversely, in C and C++ a T[] is directly a pointer to a memory block (iteration over which happens to be done in sizeof(T) byte chunks).

    Hence your char *pairs[][2] is

    • a pointer to a memory block of two elements of type char*[],
    • where each element is a pointer to another array of unknown number of elements of type char*.

    …and your Go's [][]*C.char is a slice of slices (that is struct-typed slice headers, not pointers).

    You probably need something like this:

        var argv = make([][2]*C.char, len(keypairs))
        i := 0
        for key, val := range keypairs {
            var argv2 = new([2]*C.char)
            argv2[0], argv2[1] := C.CString(key), C.CString(val)
            argv[i] = argv2
            i++
        }
        C.writeKeyValuePairs(&argv[0])
    

    Here we allocate a slice of arrays (of length 2). You correctly obtain the address of the 1st element of the enclosing array when calling the C side, so having a slice at the top level is just fine.

    The problem with this code is that you do not communicate the length of the data in argv to writeKeyValuePairs and neither do you use sentinel values at the end of that array, but I hope you've just elided that bit from your example.

    I also fail to grasp why you deallocate the memory allocated with C.CString right away. The manual says:

    // 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
    

    so you appear to deallocate the memory blocks of your key/value pair right after pointers to them were assigned to the corresponding pair of array entries. I suspect it's a bug: deallocation must take place after the C function finished execution.


    Update as of 26-07-2019:

    Here's the working code:

    package main
    
    /*
        #include <stdio.h>
        #include <stdlib.h>
    
        void writeKeyValuePairs(char *v[][2], size_t len) {
            int i;
            for (i = 0; i < len; i++) {
                printf("i=%i, k=%s, v=%s\n", i, v[i][0], v[i][1]);
            }
            return;
        }
    */
    import "C"
    
    import "unsafe"
    
    func main() {
        tags := map[string]string{
            "foo": "42",
            "bar": "12",
        }
    
        var argv = make([][2]*C.char, len(tags))
        i := 0
        for key, val := range tags {
            var kv [2]*C.char
            kv[0], kv[1] = C.CString(key), C.CString(val)
            argv[i] = kv
            i++
        }
    
        defer func(items [][2]*C.char) {
            for i := range items {
                k, v := items[i][0], items[i][1]
                C.free(unsafe.Pointer(k))
                C.free(unsafe.Pointer(v))
            }
        }(argv)
    
        C.writeKeyValuePairs(&argv[0], C.size_t(len(tags)))
    }
    

    The result:

    $ go build c.go
    $ ./c
    i=0, k=foo, v=42
    i=1, k=bar, v=12