0

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?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Jon Scobie
  • 490
  • 4
  • 10
  • You're intentionally bypassing any safety checks. You need to properly convert your `[]string` first. See the answer to https://stackoverflow.com/questions/45997786/passing-array-of-string-as-parameter-from-go-to-c-function – Marc Jan 10 '18 at 13:42
  • The runtime panics because passing pointers to Go allocated memory is not allowed. While the GC doesn't move memory (yet), it's far too easy for it to collect a value that goes out of scope as it's being passed to a C function. – JimB Jan 10 '18 at 13:46

1 Answers1

1

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
Marc
  • 19,394
  • 6
  • 47
  • 51
  • The problem is when using swig, it deals with the raw go pointers by using the same underlying type in C. I don't really understand why it all works when just using s as in my given example and fails with ns. I understand your given solution but need to now work it into the swig bindings. – Jon Scobie Jan 10 '18 at 14:27