Ошибка выполнения с cgo и некоторыми фрагментами строки

Я убрал проблему, с которой столкнулся, когда оборачивал некоторый код C для работы с golang с использованием swig, но проблема не связана с swig.

Я могу передать срез базовой строки, но как только я создаю срез с чем-либо, кроме базовых строк, я получаю панику: ошибка времени выполнения: аргумент cgo имеет указатель Go на указатель Go.

go version go1.8.5 linux/amd64

Это пример кода и его вывод

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

Как видите, первый фрагмент строки работает нормально, но как только я делаю что-то кроме основных строк, он терпит неудачу. Я пытался сначала создать новые строки, прежде чем добавлять их в срез, но проблема остается.

Что я делаю не так?


person Jon Scobie    schedule 10.01.2018    source источник
comment
Вы намеренно обходите все проверки безопасности. Сначала вам нужно правильно преобразовать []string. См. ответ на stackoverflow.com/questions/45997786/   -  person Marc    schedule 10.01.2018
comment
Среда выполнения паникует, потому что передача указателей на выделенную Go память не разрешена. Хотя сборщик мусора не перемещает память (пока), ему слишком легко собрать значение, выходящее за рамки области видимости при его передаче в функцию C.   -  person JimB    schedule 10.01.2018


Ответы (1)


Вы в основном передаете необработанные указатели Go. Вместо этого вы должны сами создавать массивы C.

Как правило, если вы увидите unsafe практически где угодно, это должно вызвать у вас подозрения. Это редко бывает правильным решением проблем с cgo.

Использование помощников из Передача массива строк в качестве параметра из функции go to C и использовать их в своем коде:

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)))
}

Вывод теперь такой, как ожидалось:

$ ./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
person Marc    schedule 10.01.2018
comment
Проблема в том, что при использовании swig он работает с необработанными указателями go, используя тот же базовый тип в C. Я действительно не понимаю, почему все это работает, когда просто используется s, как в моем примере, и терпит неудачу с ns. Я понимаю ваше решение, но теперь мне нужно включить его в привязки swig. - person Jon Scobie; 10.01.2018