缘由

现在有个应用场景,我们要在go中获取C结构体数组中的值。在C语言中,对于结构体数组,我们可以利用指针的偏移量来获取我们想要的值的位置。在go中我们怎么使用C指针和偏移量进行运算呢?下面的文字中C指的是C语言

利用cgo作为桥梁,遍历C结构体数组

unsafe.Pointer在C语言中类似void*,但是在go中无法用unsafe.Pointer进行运算。 不过go中有一种类型uintptr可以与unsafe.Pointer进行互相转换,并且可以进行运算。于是我有如下思路:

  • 利用unsafe.Pointer将C指针转化为go指针,也就是获取结构体数组的地址- 然后转成uintptr
  • 计算出元素偏移量,根据元素偏移量取出元素

举例

声明一个C数组

#include <stdio.h>
#include <stdlib.h>
typedef struct {<!-- -->
	char *name;
	int age;
}person;

//new一个长度为n的person结构体数组
person* NewPersonArray(int n)
{<!-- -->
	person *pon;
	pon =(person*)malloc(sizeof(person)*n);
	//初始化name
	for(int i = 0; i<n;i++){<!-- -->
	  pon[i].name = (char*)malloc(sizeof(char)*10);
	  pon[i].age = i;
      sprintf(pon[i].name, "name:%d", i);
	}
	return pon;
}

//释放内存
void freePersonArray(person* pon, int len)
{<!-- -->
	for (int i = 0; i < len; i ++){<!-- -->
		free(pon[i].name);
	}
	free(pon);
}

C已经做好了内存分配和内存释放,接下来看看在go中 怎么使用C的数据

func main()  {<!-- -->
	var person *C.person
	n := 10
	person = C.NewPersonArray(C.int(n))  //new一个长度为n的person数组

	//go的方式遍历C数组
	for i:=0; i < n; i++ {<!-- -->
		p := *(*C.person)(unsafe.Pointer(uintptr(unsafe.Pointer(person)) + uintptr(C.sizeof_person*C.int(i))))
		fmt.Printf("i: %d, p.name: %s, p.age: %d\n", i, C.GoString(p.name), int(p.age) )
	}
	C.freePersonArray(person, C.int(n))
}

关键的一步是计算偏移量并进行运算:

p := *(*C.person)(unsafe.Pointer(uintptr(unsafe.Pointer(person)) + uintptr(C.sizeof_person*C.int(i))))

我们来把这个步骤分解一下:
第一步,将C指针转化为unsafe.Pointer,然后转化为uintptr

uintptr(unsafe.Pointer(person))

第二步,计算每个结构体所占用的大小 C.sizeof_person,并乘以 i,然后转化为uintptr,得到偏移量, 这个person是结构体名字。

uintptr(C.sizeof_person*C.int(i))

第三步,把第一步的uintptr和第二步得到的偏移量相加,得到一个新的uintptr值,

uintptr(unsafe.Pointer(person)) + uintptr(C.sizeof_person*C.int(i))

第四步,把第三部得到的uintptr转化为unsafe.Pointer,然后再强制转化为*C.person,这就类似C语言中的指针运算:指针+偏移量

p := *(*C.person)(unsafe.Pointer(uintptr(unsafe.Pointer(person)) + uintptr(C.sizeof_person*C.int(i))))

完整代码

通过上面的四个步骤就可以利用go语言从C结构体数组中取出元素,完整代码如下:

package main
/*
#include <stdio.h>
#include <stdlib.h>
typedef struct {
	char *name;
	int age;
}person;

person* NewPersonArray(int n)
{
	person *pon;
	pon =(person*)malloc(sizeof(person)*n);
	//初始化name
	for(int i = 0; i<n;i++){
	  pon[i].name = (char*)malloc(sizeof(char)*10);
	  pon[i].age = i;
      sprintf(pon[i].name, "name:%d", i);
	}
	return pon;
}
void freePersonArray(person* pon, int len)
{
	for (int i = 0; i < len; i ++){
		free(pon[i].name);
	}
	free(pon);
}
*/
import "C"
import (
	"fmt"
	"unsafe"
)

func main()  {<!-- -->
	var person *C.person
	n := 10
	person = C.NewPersonArray(C.int(n))  //new一个长度为n的person数组

	//go的方式遍历C数组
	for i:=0; i < n; i++ {<!-- -->
		p := *(*C.person)(unsafe.Pointer(uintptr(unsafe.Pointer(person)) + uintptr(C.sizeof_person*C.int(i))))
		fmt.Printf("i: %d, p.name: %s, p.age: %d\n", i, C.GoString(p.name), int(p.age) )
	}
	C.freePersonArray(person, C.int(n))

}

输出

i: 0, p.name: name:0, p.age: 0
i: 1, p.name: name:1, p.age: 1
i: 2, p.name: name:2, p.age: 2
i: 3, p.name: name:3, p.age: 3
i: 4, p.name: name:4, p.age: 4
i: 5, p.name: name:5, p.age: 5
i: 6, p.name: name:6, p.age: 6
i: 7, p.name: name:7, p.age: 7
i: 8, p.name: name:8, p.age: 8
i: 9, p.name: name:9, p.age: 9

--完--