将C结构体转换为Go结构体的良好或推荐方法

8

我正在使用cgo开发从Go语言绑定库。 下面考虑C结构体和Go结构体。

struct cons_t {
  size_t type;
  cons_t *car;
  cons_t *cdr;
};

cons_t* parse(const char *str);

这是Go语言的结构体。
type Cons struct {
  type int;
  car *Cons;
  cdr *Cons;
}

对于以下实现Go函数的内容,有什么更好的方法来实现TranslateCCons2GoCons?

func Parse (str string) *Cons {
  str_ptr := C.CString(string);
  cons_ptr := C.parse(str_ptr);
  retCons := TranslateCCons2GoCons(cons_ptr);
  return retCons;
}

我的第一个回答如下。

/*#cgo
int getType(cons_t *cons) {
    return cons->type;
}
cons_t *getCar(cons_t *cons) {
  return cons->car;
}
cons_t *getCdr(cons_t *cons) {
  return cons->cdr;
}
*/

func TranslateCCons2GoCons (c *C.cons_t) Cons {
  type := C.getType(c);
  car := C.getCar(c);
  cdr := C.getCdr(c);
  // drop null termination for simplicity
  return Cons{type, TranslateCCons2GoCons(car), TranslateCCons2GoCons(cdr)};
}

有更好的方法吗?

2个回答

3
您可以在Go语言中使用C结构体(不过,如果该结构体包含一个联合类型,则会变得更加复杂)。最简单的方法是:
type Cons struct {
    c C.cons_t
}

现在,在Go中,C语言中的任何函数都只是一个“passthrough”。

func Parse(s string) Cons {
    str := C.CString(s)
    // Warning: don't free this if this is stored in the C code
    defer C.free(unsafe.Pointer(str))
    return Cons{c: C.parse(str)}
}

这样做会增加一些额外的开销,因为您需要在访问元素时进行类型转换。因此,之前的var c Cons{}; c.Type现在变成了:

func (c Cons) Type() int {
    return int(c.c.type)
}

中间的折衷方案是将字段存储在C类型旁边,以便于访问。

type Cons struct {
    type int
    c C.cons_t
}

func (c *Cons) SetType(t int) {
    c.type = t
    c.c.type = C.size_t(t)
}

func (c Cons) Type() int {
    return c.type
}

这样做的唯一真正问题在于,如果您经常调用C函数,这可能会增加设置Go端字段的维护开销。
func (c *Cons) SomeFuncThatAltersType() {
    C.someFuncThatAltersType(&c.c)
    c.Type = int(c.c.type) // now we have to remember to do this
}

是的,每次通过C函数访问似乎对我来说都很难维护。我更喜欢中间方式。 - shinpei

1
我建议不要使用访问器函数。您应该能够直接访问C结构体的字段,这将避免Go到C函数调用开销(这是非常重要的)。因此,您可以使用类似以下的内容:
func TranslateCCons2GoCons (c *C.cons_t) *Cons {
    if c == nil {
        return nil
    }
    return &Cons{
        type: int(c.type),
        car: TranslateCCons2GoCons(c.car),
        cdr: TranslateCCons2GoCons(c.cdr),
    }
}

此外,如果您使用C.CString分配了一个C字符串,您需要释放它。因此,您的Parse函数应该像这样:
func Parse (str string) *Cons {
    str_ptr := C.CString(str)
    defer C.free(unsafe.Pointer(str_ptr)
    cons_ptr := C.parse(str_ptr)
    retCons := TranslateCCons2GoCons(cons_ptr)
    // FIXME: Do something to free cons_ptr here.  The Go runtime won't do it for you
    return retCons
}

当然,只有在它没有被 parse 存储的情况下才可以释放它!:) 在这种情况下,通常会将 CString 存储在未导出的字段中,并稍后调用 C.free - Linear
@JamesHenstridge 谢谢。这看起来简单多了。 - shinpei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接