• CGO,基本数据类型转换2 和 函数调用


    CGO 涉及的数据类型转换包含一下内容:

    • 数值类型
    • 字符串和切片类型
    • 结构体、联合体、枚举类型‘
    • 数组类型
    • 指针类型
    • 数组和指针间的转换
    • 切片和切片之间的转换

    前面 3 个咱们在上一篇短文已经梳理到了,接下来继续

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5lP6xgbk-1660485524922)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4f6b12f88ab4bc49ea9703737f3c624~tplv-k3u1fbpfcp-zoom-1.image)]

    数组类型

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mn7Tn13U-1660485524924)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df03197cca6f478f82b5d26bcc5f641b~tplv-k3u1fbpfcp-zoom-1.image)]

    数值和指针间的转换

    在 cgo 里面,如何实现数值和指针的转换呢?

    GO 语言里面:

    禁止将数值类型直接转为指针类型

    但是有了 cgo ,我们就有办法了,go 中 对unsafe.Pointer指针类型特别定义了一个uintptr 类型 ,我们仍然是将他们作为桥梁,进行转换成我们目的指针

    例如,咱们一个 GO 里面的 int32 的数值,如何转换成 C 里面的 指针呢?

    就像上面说到的,咱们利用好这个桥梁,将 int32 转成 uintptr,再转成 unsafe.pointer,最后转成 C 的 char 指针

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kBQnj0hU-1660485524925)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a56f7e9104343e982b1ee391ac06f11~tplv-k3u1fbpfcp-zoom-1.image)]

    切片和切片之间的转换

    在 cgo 里面,如何实现切片和切片之间的转换呢?

    切片和切片之间的转换就要用到 GO 里面 reflect 包提供的数据结构了,

    因为 GO 里面,数组或者切片已经不是指针类型了,需要通利用 reflect 里面的数据结构来进行转换,如下:

    //它的引用不会被垃圾回收,因此程序必须使用正确类型的指向底层数据的指针
    type StringHeader struct {
        Data uintptr
        Len  int
    }
    
    //它的引用不会被垃圾回收,因此程序必须使用正确类型的指向底层数据的指针
    type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eg5UaTdk-1660485524927)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5d475f40648d48bcbf328a6156e57f17tplv-k3u1fbpfcp-zoom-1.image)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ARtd7M0-1660485524933)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8343e87fce84436b87200fae8283a42etplv-k3u1fbpfcp-zoom-1.image)]

    切片 A 如何转换成 切片 B?

    我们可以这样来进行转换,先构造一个空的切片

    var p []int
    var q []string
    
    pk := (*reflect.SliceHeader)(unsafe.Pointer(&p))
    qk := (*reflect.SliceHeader)(unsafe.Pointer(&q))
    
    • 1
    • 2
    • 3
    • 4
    • 5

    再用原来的数据来填充这个空切片,此处需要注意 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])
    
    • 1
    • 2
    • 3

    函数调用

    基本的函数调用我们在上一篇短文已经演示过了,咱们来看看其他的

    • C 函数自身的返回值,在 GO 里面是如何应用的

    C 函数自身的返回值,在 GO 里面是如何应用的

    咱们写一个有返回值的 C 函数,然后 GO 再去调用:

    C 语言不支持多个返回结果,但是 GO 语言支持返回过个结果,CGO 里面 我们可以用 标准库里面的 errno 宏用于返回错误状态

    package main
    
    /*
    #include 
    
    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    执行结果如下:

    0 invalid argument
    3 <nil>
    
    • 1
    • 2

    若 C 的函数是无返回值的,我们同样是可以这样处理的。

    例如可以是这样

    v, _ := C.xxx()
    fmt.Printf("%#v", v)    // 输出为 main._Ctype_void{}
    fmt.Println(C.xxx())    // 输出为 [] 0长的数组类型[0]byte
    
    • 1
    • 2
    • 3

    咱们实际实践了之后,发现 C 语言的v oid 类型对应的是当前的 main 包中的_Ctype_void类型

    滴水穿石,一步一步的学

    参考资料:

    GO 高级编程

    欢迎点赞,关注,收藏

    朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-poMlndCm-1660485524936)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/335fd35d7699432a837b090cfd0cc5af~tplv-k3u1fbpfcp-zoom-1.image)]

    好了,本次就到这里

    技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

    我是阿兵云原生,欢迎点赞关注收藏,下次见~

  • 相关阅读:
    国际自动机工程师学会(SAE International)战略投资几何伙伴
    CentOS7 设置 MySQL 主备同步
    2023年中国中端连锁酒店分类、市场规模及主要企业市占率[图]
    沸点 | Ultipa 图数据库金融应用场景优秀案例首批入选,金融街论坛年会发布
    基于Java+SpringBoot+Vue前后端分离秒杀系统设计和实现
    python加密Django框架代码(通过修改Cpython解释器)
    搞定面试官 - 你可以介绍一下在 MySQL 中,哪些情况下 索引会失效嘛?
    什么是快速失败(fail-fast)和安全失败(fail-safe)?
    出现这个页面的问题是什么
    TS项目实战三:Express实现登录注册功能后端
  • 原文地址:https://blog.csdn.net/m0_37322399/article/details/126337093