英文:
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
。正如你所示,该数组的内容保存了联合体的内容,并且使用它只需要进行指针转换。
然而,你可以使用_数组的地址_来大大简化这个过程。对于名为data
的C._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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论