Reputation: 75
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
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
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
char*[]
,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