如何在cgo中处理来自打包结构的char *?

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

How to handle char * from packed struct in cgo?

问题

由于Go语言不支持紧凑结构体(packed struct),我找到了一篇很棒的文章,用例子解释了如何在Go中使用紧凑结构体。链接在这里:https://medium.com/@liamkelly17/working-with-packed-c-structs-in-cgo-224a0a3b708b

问题是,当我尝试将char *替换为[10]char时,它不起作用。我不确定这个转换是如何处理[10]char而不是char *的。下面是从上述文章中提取并修改为char *的示例代码。

package main

/*
#include "stdio.h"
#pragma pack(1)
typedef struct{
	unsigned char a;
	char b;
	int c;
	unsigned int d;
	char *e; // 从 char[10] 改为 char *
}packed;

void PrintPacked(packed p){
	printf("\nFrom C\na:%d\nb:%d\nc:%d\nd:%d\ne:%s\n", p.a, p.b, p.c, p.d, p.e);
}

*/
import "C"
import (
	"bytes"
	"encoding/binary"
)

// GoPack 是C中紧凑结构体的Go版本
type GoPack struct {
	a uint8
	b int8
	c int32
	d uint32
	e [10]uint8
}

// Pack 生成Go结构体的紧凑版本
func (g *GoPack) Pack(out *C.packed) {
	buf := &bytes.Buffer{}
	binary.Write(buf, binary.LittleEndian, g)
	*out = *(*C.packed)(C.CBytes(buf.Bytes()))
}

func main() {
	pack := &GoPack{1, 2, 3, 4, [10]byte{}}
	copy(pack.e[:], "TEST123")
	cpack := C.packed{} // 仅用于分配内存,仍然受GC控制
	pack.Pack(&cpack)
	C.PrintPacked(cpack)
}

这是我第一次使用cgo,如果我在任何地方出错,请纠正我。

英文:

Since Go doesn’t support packed struct I found this great article explains everything with examples how to work with packed struct in go. https://medium.com/@liamkelly17/working-with-packed-c-structs-in-cgo-224a0a3b708b

The problem is when I try char * in place of [10]char it's not working. I'm not sure how this conversion works with [10]char and not with char * . Here is example code taken from above article and modified with char * .

package main

/*
#include "stdio.h"
#pragma pack(1)
typedef struct{
	unsigned char a;
	char b;
	int c;
	unsigned int d;
	char *e; // changed from char[10] to char *
}packed;

void PrintPacked(packed p){
	printf("\nFrom C\na:%d\nb:%d\nc:%d\nd:%d\ne:%s\n", p.a, p.b, p.c, p.d, p.e);
}

*/
import "C"
import (
	"bytes"
	"encoding/binary"
)

//GoPack is the go version of the c packed structure
type GoPack struct {
	a uint8
	b int8
	c int32
	d uint32
	e [10]uint8
}

//Pack Produces a packed version of the go struct
func (g *GoPack) Pack(out *C.packed) {
	buf := &bytes.Buffer{}
	binary.Write(buf, binary.LittleEndian, g)
	*out = *(*C.packed)(C.CBytes(buf.Bytes()))
}

func main() {
	pack := &GoPack{1, 2, 3, 4, [10]byte{}}
	copy(pack.e[:], "TEST123")
	cpack := C.packed{} //just to allocate the memory, still under GC control
	pack.Pack(&cpack)
	C.PrintPacked(cpack)
}

I'm working with cgo first time so correct me if i am wrong at any point.

答案1

得分: 1

你正在将 GoPack.e 的十个(零)字节写入类型为 char *packed.e 中。这样做是行不通的,因为指针的大小取决于你的系统,通常为4或8个字节。即使这些字节表示一个有效的指针,你也会溢出分配的内存空间。

如果你想创建一个具有有效 packed.e 字段的结构体,你需要在 C 堆上分配10个字节的内存,将字节复制到其中,然后将 packed.e 指向这个分配的内存(在释放相应的 packed 结构体时,你也需要释放这块内存)。你不能直接使用 binary.Write 来实现这一点。

你可以以此作为起点:

buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, g.a)
binary.Write(buf, binary.LittleEndian, g.b)
binary.Write(buf, binary.LittleEndian, g.c)
binary.Write(buf, binary.LittleEndian, g.d)
binary.Write(buf, binary.LittleEndian, uintptr(C.CBytes(g.e)))
*out = *(*C.packed)(C.CBytes(buf.Bytes()))

函数 C.CBytes(b) 在 C 堆上分配了 len(b) 个字节的内存,并将 b 中的字节复制到其中,返回一个 unsafe.Pointer

请注意,我从你的代码中复制了 *out = *(*C.packed)... 这一行。实际上,这会导致内存泄漏和不必要的复制。也许更好的做法是使用一个直接将字节写入 out 指向的内存的写入器。

也许可以这样做:

const N = 10000 // 应该是 sizeof(*out) 或更大
buf := bytes.NewBuffer((*[N]byte)(unsafe.Pointer(out))[:])

这样就创建了一个 bytes.Buffer,直接将数据写入 out 结构体,而不经过任何中间内存。请注意,由于使用了不安全的操作,如果写入的数据字节数超过 out 指向的内存大小,就会发生缓冲区溢出。

警告:这些操作都相当危险,容易出现与 C 语言相同的问题,而且你需要检查 cgo 指针规则,以确保不会受到垃圾回收的影响。建议你避免编写或包含此类代码,因为它可能引入隐蔽的问题,而且可能不会立即显现出来,尤其是考虑到你说自己对指针和内存分配的经验不多。

英文:

You are writing ten (zero) bytes of GoPack.e into the packed.e which is of type char *. This won't work, because pointers will be 4 or 8 bytes depending on your system, so even if the bytes represented a valid pointer, you are overflowing the amount of memory allocated.

If you want to create a valid structure with a valid packed.e field, you need to allocate 10 bytes of memory in the C heap, copy the bytes into that, and then point packed.e to this allocated memory. (You will also need to free this memory when you free the corresponding packed structure). You can't do this directly with binary.Write.

You can take this as a starting point:

buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, g.a)
binary.Write(buf, binary.LittleEndian, g.b)
binary.Write(buf, binary.LittleEndian, g.c)
binary.Write(buf, binary.LittleEndian, g.d)
binary.Write(buf, binary.LittleEndian, uintptr(C.CBytes(g.e))
*out = *(*C.packed)(C.CBytes(buf.Bytes()))

The function C.CBytes(b) allocates len(b) bytes in the C heap, and copies the bytes from b into it, returning an unsafe.Pointer.

Note that I've copied your *out = *(*C.packed)... line from your code. This actually causes a memory leak and an unnecessary copy. Probably it would be better to use a writer that writes bytes directly to the memory pointed to by out.

Perhaps this?

const N = 10000 // should be sizeof(*out) or larger
buf := bytes.NewBuffer((*[N]byte)(unsafe.Pointer(out))[:])

This makes a bytes.Buffer that directly writes to the out struct without going through any intermediate memory. Note that because of unsafe shenanigans, this is vulnerable to a buffer overflow if you write more bytes of data than is pointed to by out.

Words of warning: this is all pretty nasty, and prone to the same sorts of problems you'd find in C, and you'd need to check the cgo pointer rules to make sure that you're not vulnerable to garbage collection interactions. A point of advice: given that you say you "don't have much experience with pointers and memory allocation", you probably should avoid writing or including code like this because the problems it can introduce are nefarious and may not be immediately obvious.

huangapple
  • 本文由 发表于 2020年8月7日 14:55:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/63296692.html
匿名

发表评论

匿名网友

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

确定