Stoyvo
Stoyvo

Reputation: 75

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

Upvotes: 1

Views: 1148

Answers (2)

peterSO
peterSO

Reputation: 166569

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


Using your example,

package main

/*
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

bool writeKeyValuePairs(char *pairs[][2], size_t size) {
    for (size_t i = 0; i < size; i++ ) {
        if (fprintf(stdout, "%s: %s\n", pairs[i][0], pairs[i][1]) < 0) {
            return false;
        }
    }
    return true;
}
*/
import "C"

import (
    "fmt"
    "os"
    "unsafe"
)

func allocCPairs(m map[string]string) (*[2]*C.char, C.size_t) {
    kv := make([][2]*C.char, 0, len(m)+1)
    for k, v := range m {
        kv = append(kv, [2]*C.char{0: C.CString(k), 1: C.CString(v)})
    }
    return &kv[:cap(kv)][0], C.size_t(len(kv))
}

func freeCPairs(cPairs *[2]*C.char, size C.size_t) {
    gPairs := (*[1 << 30][2]*C.char)(unsafe.Pointer(cPairs))[:size]
    for _, pair := range gPairs {
        for _, p := range pair {
            C.free(unsafe.Pointer(p))
        }
    }
}

func writeKeyValuePairs(m map[string]string) error {
    cPairs, size := allocCPairs(m)
    defer freeCPairs(cPairs, size)

    rv := C.writeKeyValuePairs(cPairs, size)
    if !rv {
        return fmt.Errorf("writeKeyValuePairs: write error")
    }
    return nil
}

func main() {
    m := map[string]string{
        "K1": "V1",
        "K2": "V2",
        // ...
        "Kn": "Vn",
    }
    fmt.Println(m)

    err := writeKeyValuePairs(m)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }
}

Output:

map[K1:V1 K2:V2 Kn:Vn]
K1: V1
K2: V2
Kn: Vn

Upvotes: 1

kostix
kostix

Reputation: 55453

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

Upvotes: 4

Related Questions