Reputation: 580
I have stripped back a problem I have come across whilst wrapping some C code to work with golang using swig but the problem doesn't rest with swig.
I can pass in a basic string slice but as soon as I construct the slice with anything other than basic strings, I get a panic: runtime error: cgo argument has Go pointer to Go pointer.
go version go1.8.5 linux/amd64
This is the sample code and its output
package main
import (
"fmt"
"reflect"
"unsafe"
)
/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct { char *p; int n; } _gostring_;
typedef struct { void* array; int len; int cap; } _goslice_;
void prtText(char * const *txt, int len)
{
int i = 0;
for ( i=0; i<len; i++ ) {
printf("Text %d is: %s\n", i, txt[i]);
}
}
void _wrap_printText(_goslice_ _swig_go_0) {
_gostring_ *p;
char **arg1 = (char **)calloc(_swig_go_0.len, sizeof(char*));
if (arg1) {
for (int i=0; i<_swig_go_0.len; i++) {
p = &(((_gostring_*)_swig_go_0.array)[i]);
arg1[i] = calloc(1,(p->n)+1);
strncpy(arg1[i], p->p, p->n);
}
}
int arg2 = _swig_go_0.len;
prtText((char *const *)arg1,arg2);
}
*/
import "C"
func PrintText(arg1 []string) {
C._wrap_printText(*(*C._goslice_)(unsafe.Pointer(&arg1)))
}
func main() {
s := []string{}
s = append(s, "blah")
s = append(s, "hello")
s = append(s, "again")
ns := []string{}
ns = append(ns, "ns: "+s[0])
ns = append(ns, "ns: "+s[1])
ns = append(ns, "ns: "+s[2])
fmt.Println("type s:", reflect.TypeOf(s))
fmt.Println("type ns:", reflect.TypeOf(ns))
fmt.Println("s:", s)
fmt.Println("ns:", ns)
PrintText(s)
PrintText(ns)
}
go build -i -x -gcflags '-N -l' main.go
./main
type s: []string
type ns: []string
s: [blah hello again]
ns: [ns: blah ns: hello ns: again]
Text 0 is: blah
Text 1 is: hello
Text 2 is: again
panic: runtime error: cgo argument has Go pointer to Go pointer
As you can see, the first string slice works fine but as soon as I do anything other than basic strings, it fails. I've tried making new strings first before appending them to the slice but the problem remains.
What am I doing wrong?
Upvotes: 0
Views: 539
Reputation: 21055
You're basically passing the raw Go pointers.
Instead, you should build C
arrays yourself.
As a general rule, seeing unsafe
pretty much anywhere should make you suspicious. It is rarely the right way around issues with cgo.
Using the helpers from Passing array of string as parameter from go to C function and using them in your code:
package main
import (
"fmt"
"reflect"
)
/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void prtText(char * const *txt, int len)
{
int i = 0;
for ( i=0; i<len; i++ ) {
printf("Text %d is: %s\n", i, txt[i]);
}
}
static char**makeCharArray(int size) {
return calloc(sizeof(char*), size);
}
static void setArrayString(char **a, char *s, int n) {
a[n] = s;
}
static void freeCharArray(char **a, int size) {
int i;
for (i = 0; i < size; i++)
free(a[i]);
free(a);
}
*/
import "C"
func main() {
s := []string{}
s = append(s, "blah")
s = append(s, "hello")
s = append(s, "again")
ns := []string{}
ns = append(ns, "ns: "+s[0])
ns = append(ns, "ns: "+s[1])
ns = append(ns, "ns: "+s[2])
fmt.Println("type s:", reflect.TypeOf(s))
fmt.Println("type ns:", reflect.TypeOf(ns))
fmt.Println("s:", s)
fmt.Println("ns:", ns)
sargs := C.makeCharArray(C.int(len(s)))
defer C.freeCharArray(sargs, C.int(len(s)))
for i, p := range s {
C.setArrayString(sargs, C.CString(p), C.int(i))
}
nsargs := C.makeCharArray(C.int(len(ns)))
defer C.freeCharArray(nsargs, C.int(len(ns)))
for i, p := range ns {
C.setArrayString(nsargs, C.CString(p), C.int(i))
}
C.prtText(sargs, C.int(len(s)))
C.prtText(nsargs, C.int(len(ns)))
}
The output is now as expected:
$ ./main
type s: []string
type ns: []string
s: [blah hello again]
ns: [ns: blah ns: hello ns: again]
Text 0 is: blah
Text 1 is: hello
Text 2 is: again
Text 0 is: ns: blah
Text 1 is: ns: hello
Text 2 is: ns: again
Upvotes: 1