Golang CGo:将联合字段转换为Go类型

huangapple go评论89阅读模式
英文:

Golang CGo: converting union field to Go type

问题

我正在64位平台上使用这个C结构体,尝试访问value联合中的ui32v字段:

struct _GNetSnmpVarBind {
  guint32		*oid;		/* 变量的名称 */
  gsize		oid_len;	/* 名称的长度 */
  GNetSnmpVarBindType	type;		/* 变量类型/异常 */
  union {
    gint32   i32;			/* 32位有符号整数   */
    guint32  ui32;			/* 32位无符号整数 */
    gint64   i64;			/* 64位有符号整数   */
    guint64  ui64;			/* 64位无符号整数 */
    guint8  *ui8v;			/*  8位无符号整数向量 */
    guint32 *ui32v;			/* 32位无符号整数向量 */
  }			value;		/* 变量的值 */
  gsize		value_len;	/* 向量的字节长度 */
};

我可以为每个联合元素编写一个C包装函数,但出于教学目的,我更愿意使用Go来工作。这是我尝试访问ui32v字段的方式:

func union_to_guint32_ptr(cbytes [8]byte) (result *_Ctype_guint32) {
  buf := bytes.NewBuffer(cbytes[:])
  var ptr uint64
  if err := binary.Read(buf, binary.LittleEndian, &ptr); err == nil {
    return (*_Ctype_guint32)(unsafe.Pointer(ptr))
  }
  return nil
}

然而,这会导致错误无法将ptr(类型为uint64)转换为类型unsafe.Pointer

那么我该如何将uint64转换为指向C guint32的Go类型呢?我尝试过各种组合,如先将其转换为uintptr,然后再转换为*_Ctype_guint32,先将其转换为uintptr,然后使用unsafe.Pointer,...

我的推理是:我传递了一个8字节的数组。将其转换为uint64,那就是内存地址。将其强制转换为指向guint32的指针(即C中的guint32数组),并将其作为结果返回-这就是联合字段"value"作为guint32 *。


上下文

稍后,我将使用value_len字段将C guint32数组转换为字符串,使用我已经知道有效的函数:

guint32_star := union_to_guint32_ptr(data.value)
result += OidArrayToString(guint32_star, data.value_len)

这段C代码来自gsnmp

英文:

I'm working with this C struct on a 64 bit platform, trying to access the ui32v field in the value union:

struct _GNetSnmpVarBind {
  guint32		*oid;		/* name of the variable */
  gsize		oid_len;	/* length of the name */
  GNetSnmpVarBindType	type;		/* variable type / exception */
  union {
    gint32   i32;			/* 32 bit signed   */
    guint32  ui32;			/* 32 bit unsigned */
    gint64   i64;			/* 64 bit signed   */
    guint64  ui64;			/* 64 bit unsigned */
    guint8  *ui8v;			/*  8 bit unsigned vector */
    guint32 *ui32v;			/* 32 bit unsigned vector */
  }			value;		/* value of the variable */
  gsize		value_len;	/* length of a vector in bytes */
};

I could write a C wrapper function for each union element but for didactic purposes I'd rather work in Go. Here's how I'm trying to access the ui32v field:

func union_to_guint32_ptr(cbytes [8]byte) (result *_Ctype_guint32) {
  buf := bytes.NewBuffer(cbytes[:])
  var ptr uint64
  if err := binary.Read(buf, binary.LittleEndian, &ptr); err == nil {
    return (*_Ctype_guint32)(unsafe.Pointer(ptr))
  }
  return nil
}

However this gives an error cannot convert ptr (type uint64) to type unsafe.Pointer

So how do I convert a uint64 to a Go type that points to a C guint32? I've tried various combinations of casting to a uintptr then casting to a *_Ctype_guint32, casting to a uintptr then using unsafe.Pointer, ...

My reasoning is: I'm passed an array of 8 bytes. Convert that to a uint64, that's the memory address. Cast that to a pointer to a guint32 (ie a C array of guint32's), and return that as a result - that is the union field "value" as a guint32 *.


Context

Later I'll want to convert the C array of guint32's to a string utilising the value_len field, using a function I know already works:

guint32_star := union_to_guint32_ptr(data.value)
result += OidArrayToString(guint32_star, data.value_len)

The C code is from gsnmp.

答案1

得分: 7

cgo将一个联合体公开为足够大以容纳联合体最大成员的字节数组。在你的情况下,这是64位,即8个字节,[8]byte。正如你所示,该数组的内容保存了联合体的内容,并且使用它只需要进行指针转换。

然而,你可以使用_数组的地址_来大大简化这个过程。对于名为dataC._GNetSnmpVarBind

guint32_star := *(**C.guint32)(unsafe.Pointer(&data.value[0]))

我第一次看到这个时候并没有完全理解,但当我将其分解时,它变得更加清晰:

var data C._GNetSnmpVarBind    // C结构体
var union [8]byte = data.value // 联合体,作为连续的八个字节的内存

// 第一个魔法。连续内存中第一个元素的地址就是该内存的地址。换句话说,就是该联合体的地址。
var addr *byte = &union[0]

// 第二个魔法。我们可以通过改变指针的类型为*T来指向一些有用的类型T,而不是指向内存的字节,使用unsafe.Pointer。在这种情况下,我们想要将联合体解释为成员`guint32 *ui32v`。也就是说,T = (*C.guint32),*T = (**C.guint32)。
var cast **C.guint32 = (**C.guint32)(unsafe.Pointer(addr))

// 最后一步。我们想要的是联合体的内容,而不是联合体的地址。解引用它!
var guint32_star *C.guint32 = *cast

感谢Alan Shen的文章,它以一种终于让我理解的方式描述了cgo中联合体的表示。

英文:

cgo exposes a union as a byte array large enough to hold the largest member of the union. In your case that is 64 bits which are 8 bytes, [8]byte. As you've demonstrated, the contents of this array hold the contents of the union and using it is a matter of pointer conversion.

However, you can use the address of the array to greatly simplify the process. For a C._GNetSnmpVarBind named data,

guint32_star := *(**C.guint32)(unsafe.Pointer(&data.value[0]))

I didn't fully understand this the first time I saw it, but it became more clear when I broke it down:

var data C._GNetSnmpVarBind    // The C struct
var union [8]byte = data.value // The union, as eight contiguous bytes of memory

// The first magic. The address of the first element in that contiguous memory
// is the address of that memory. In other words, the address of that union.
var addr *byte = &union[0]

// The second magic. Instead of pointing to bytes of memory, we can point
// to some useful type, T, by changing the type of the pointer to *T using
// unsafe.Pointer. In this case we want to interpret the union as member
// `guint32 *ui32v`. That is, T = (*C.guint32) and *T = (**C.guint32).
var cast **C.guint32 = (**C.guint32)(unsafe.Pointer(addr))

// The final step. We wanted the contents of the union, not the address
// of the union. Dereference it!
var guint32_star *C.guint32 = *cast

Credit goes to Alan Shen's article which described the cgo representation of a union in a way that finally made sense to me.

答案2

得分: 5

解决方案首先是将类型转换为uintptr,然后再转换为unsafe.Pointer,即两个单独的转换:

func union_to_guint32_ptr(cbytes [8]byte) (result *_Ctype_guint32) {
    buf := bytes.NewBuffer(cbytes[:])
    var ptr uint64
    if err := binary.Read(buf, binary.LittleEndian, &ptr); err == nil {
        uptr := uintptr(ptr)
        return (*_Ctype_guint32)(unsafe.Pointer(uptr))
    }
    return nil
}

我通过与命令行工具的结果进行比较来验证这一点,它返回了正确的结果。


上下文

// gsnmp._Ctype_gpointer -> *gsnmp._Ctype_GNetSnmpVarBind
data := (*C.GNetSnmpVarBind)(out.data)

switch VarBindType(data._type) {
case GNET_SNMP_VARBIND_TYPE_OBJECTID:
    result += "GNET_SNMP_VARBIND_TYPE_OBJECTID" + ":"
    guint32_star := union_to_guint32_ptr(data.value)
    result += OidArrayToString(guint32_star, data.value_len)
英文:

The solution was first to cast to uintptr, then cast to unsafe.Pointer ie two separate casts:

func union_to_guint32_ptr(cbytes [8]byte) (result *_Ctype_guint32) {
    buf := bytes.NewBuffer(cbytes[:])
    var ptr uint64
    if err := binary.Read(buf, binary.LittleEndian, &ptr); err == nil {
        uptr := uintptr(ptr)
        return (*_Ctype_guint32)(unsafe.Pointer(uptr))
    }   
    return nil 
}                

I checked this by comparing results with a command line tool, and it's returning correct results.


Context

// gsnmp._Ctype_gpointer -> *gsnmp._Ctype_GNetSnmpVarBind
data := (*C.GNetSnmpVarBind)(out.data)

switch VarBindType(data._type) {
case GNET_SNMP_VARBIND_TYPE_OBJECTID:
    result += "GNET_SNMP_VARBIND_TYPE_OBJECTID" + ":"
    guint32_star := union_to_guint32_ptr(data.value)
    result += OidArrayToString(guint32_star, data.value_len)

答案3

得分: 3

Sonia已经回答了自己的问题,我只想提供为什么需要两次类型转换的原因。

根据unsafe.Pointer的文档:

> 1) 任何类型的指针值都可以转换为Pointer。

> 2) Pointer可以转换为任何类型的指针值。

> 3) uintptr可以转换为Pointer。

> 4) Pointer可以转换为uintptr。

由于var ptr uint64不是指针(因为uint64类型不是指针),无法直接使用规则1将ptr转换为unsafe.Pointer。因此,需要先将ptr转换为uintptr,然后再根据规则3将uintptr转换为Pointer

英文:

Sonia already answered her own question, I just want to provide the reason for why two type conversions are necessary.

From the documentation for unsafe.Pointer:

> 1) A pointer value of any type can be converted to a Pointer.

> 2) A Pointer can be converted to a pointer value of any type.

> 3) A uintptr can be converted to a Pointer.

> 4) A Pointer can be converted to a uintptr.

Since var ptr uint64 is not a pointer (as type uint64 is not a pointer), ptr cannot be converted directly to unsafe.Pointer using rule 1. Therefore it is necessary to first convert ptr to uintptr, then from uintptr to a Pointer following rule 3.

答案4

得分: 1

从CGO文档中:

> 要直接访问结构体、联合体或枚举类型,请在其前面加上struct_、union_或enum_,如C.struct_stat。

所以我猜测(未经测试)代码可能类似于:

myUint32var := somePtrTo_GNetSnmpVarBind.union_guint32

用于访问由somePtrTo_GNetSnmpVarBind指向的结构体的联合体中的guint32成员。

英文:

From the CGO documentation:

> To access a struct, union, or enum type directly, prefix it with struct_, union_, or enum_, as in C.struct_stat.

So I guess (not tested) the code might be something similar to:

myUint32var := somePtrTo_GNetSnmpVarBind.union_guint32

for accessing the guint32 member of the union of the struct pointed to by somePtrTo_GNetSnmpVarBind

huangapple
  • 本文由 发表于 2013年1月29日 18:44:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/14581063.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定