CGO学习二,基本数据类型转换2 和 函数调用
CGO 涉及的数据类型转换包含一下内容:
- 数值类型
- 字符串和切片类型
- 结构体、联合体、枚举类型‘
- 数组类型
- 指针类型
- 数组和指针间的转换
- 切片和切片之间的转换
前面 3 个咱们在上一篇短文已经梳理到了,接下来继续
数组类型
C 语言里面:
- 数组
C 语言里面,数组名对应一个指针,指向特定类型特定长度的一段内存,但是这个指针不能被修改
C语言的字符串是一个char类型的数组,字符串的长度需要根据表示结尾的NULL字符的位置确定
- 字符串
是一个 char 类型的数组
- 切片
C 语言没有切片的概念
GO 语言里面:
- 数组
数组是一种值类型,而且数组的长度是数组类型的一个部分
- 字符串
就是一段长度确定的只读byte类型的内存
- 切片
是一个简单的动态数组
从上面我们可以看出来,C 语言 和 GO 语言的数组,切片,字符串的相互转换,就可以是指针和指针指向的内存长度的转换
CGO 官方给咱们提供了如下 5 个函数,用于 C 语言和 GO 语言互相转换:
- func C.CString(string) *C.char
C.CString
将传入的 go 字符串,克隆成一个 C 格式的字符串,克隆出来的字符串是使用 C 语言中 malloc 开辟出来的,因此我们用完了这个函数,需要手动去释放内存
- func C.CBytes([]byte) unsafe.Pointer
C.CBytes
用于将输入的 go byte 类型的数组(切片),克隆并转换成 C 语言的指针,指针的是一个数组,需要开辟空间,不用的时候,也是需要手动释放
- func C.GoString(*C.char) string
C.GoString
将 C 的字符串克隆成 GO 的 string , GO 里面自己会释放内存
- func C.GoStringN(*C.char, C.int) string
C.GoStringN
,将C 具体某个长度的字符串转换成 GO 的 string, GO 里面自己会释放内存
- func C.GoBytes(unsafe.Pointer, C.int) []byte
C.GoBytes
将 C 的数组,转换成 GO 的切片
小结:
上述一组官方提供的函数,GO 语言和 C 语言相互转换都是通过克隆的方式实现
GO 转 C
C 是通过 malloc 的方式 在 C 自己的空间中开辟内存,因此我们不需要使用的时候,需要释放
C 转 GO
也是通过克隆的方式,但是 GO 有自己的内存管理,不需要我们手动释放内存
上述函数的优势
- 以克隆的方式进行转换,内存都是在各自语言内开辟的,内存管理简单
上述函数的劣势
- 相互转换都需要额外开辟内存,增大了系统开销
指针和指针间的转换
在 cgo 里面,如何实现指针和指针间的转换呢?
C 语言里面:
指针是 C 语言的灵魂,C 语言里面,不同类型指针间可以强制转换,指针间的自由转换也是 cgo 代码中经常要解决的第一个重要的问题
GO 语言里面:
不同类型的转换非常严格,任何 C 语言中可能出现的警告信息在Go语言中都可能是错误。
GO 里面不同类型的指针是禁止转换的,但是有了 CGO 就打破了这种禁锢,可以使用 unsafe.pointer 指针类型作为桥梁来进行转换
例如
var a *A var b *B b = (*b)(unsafe.Pointer(a)) // *A => *B a = (*a)(unsafe.Pointer(b)) // *B => *A
数值和指针间的转换
在 cgo 里面,如何实现数值和指针的转换呢?
GO 语言里面:
禁止将数值类型直接转为指针类型
但是有了 cgo ,我们就有办法了,go 中 对unsafe.Pointer
指针类型特别定义了一个uintptr 类型 ,我们仍然是将他们作为桥梁,进行转换成我们目的指针
例如,咱们一个 GO 里面的 int32 的数值,如何转换成 C 里面的 指针呢?
就像上面说到的,咱们利用好这个桥梁,将 int32 转成 uintptr,再转成 unsafe.pointer,最后转成 C 的 char 指针
切片和切片之间的转换
在 cgo 里面,如何实现切片和切片之间的转换呢?
切片和切片之间的转换就要用到 GO 里面 reflect 包提供的数据结构了,
因为 GO 里面,数组或者切片已经不是指针类型了,需要通利用 reflect 里面的数据结构来进行转换,如下:
//它的引用不会被垃圾回收,因此程序必须使用正确类型的指向底层数据的指针 type StringHeader struct { Data uintptr Len int } //它的引用不会被垃圾回收,因此程序必须使用正确类型的指向底层数据的指针 type SliceHeader struct { Data uintptr Len int Cap int }
}
切片 A 如何转换成 切片 B?
我们可以这样来进行转换,先构造一个空的切片
var p []int var q []string pk := (*reflect.SliceHeader)(unsafe.Pointer(&p)) qk := (*reflect.SliceHeader)(unsafe.Pointer(&q))
再用原来的数据来填充这个空切片,此处需要注意 len 和 cap 的计算和赋值
pk.Data = qk.Data pk.Len = qk.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0]) pk.Cap = qk.Cap * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
函数调用
基本的函数调用我们在上一篇短文已经演示过了,咱们来看看其他的
- C 函数自身的返回值,在 GO 里面是如何应用的
C 函数自身的返回值,在 GO 里面是如何应用的
咱们写一个有返回值的 C 函数,然后 GO 再去调用:
C 语言不支持多个返回结果,但是 GO 语言支持返回过个结果,CGO 里面 我们可以用 标准库里面的 errno
宏用于返回错误状态
package main /* #include <errno.h> static int testadd(int a, int b) { if (a < 0){ errno = EINVAL; return 0; } return a+b; } */ import "C" import "fmt" func main() { v0, err0 := C.testadd(-2, 1) fmt.Println(v0, err0) v1, err1 := C.testadd(1, 2) fmt.Println(v1, err1) }
执行结果如下:
0 invalid argument 3 <nil>
若 C 的函数是无返回值的,我们同样是可以这样处理的。
例如可以是这样
v, _ := C.xxx() fmt.Printf("%#v", v) // 输出为 main._Ctype_void{} fmt.Println(C.xxx()) // 输出为 [] 0长的数组类型[0]byte
咱们实际实践了之后,发现 C 语言的v oid 类型对应的是当前的 main 包中的_Ctype_void
类型
滴水穿石,一步一步的学
欢迎点赞,关注,收藏
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是阿兵云原生,欢迎点赞关注收藏,下次见~