初识CGO

什么是CGO

CGO:C/C++经过几十年的发展,已经积累了海量的软件资产,它们很多久经考验而且性能足够优化。GO必须要能够站在C/C++这个巨人的肩膀之上。C语言作为一个通用工具,很多库会选择提供一个C兼容的API,然后用其他不同的编程语言实现。GO通过自带的一个CGO工具来支持C语言函数调用,同时我们可以用GO语言导出C动态库接口给其他语言使用

#### 几个简单的demo

demo1
 package main
 
 
 import "C" //启动CGO特性
 //Go build 阶段会在编译和链接阶段启动gcc编译器
 func main() {
    println("hello cgo")
 }
demo2
 package main
 
 
 //void SayHello(char* s);
 import "C"
 
 
 import (
    "fmt"
 )
 
 
 func main() {
    C.SayHello(C.CString("Hello, World\n"))
 }
 
 
 //export SayHello   //CGO指令 将SayHello导出为C语言函数
 func SayHello(s *C.char) {
    fmt.Print(C.GoString(s))
 }
demo3
 package main
 
 
 /*
 #include<stdio.h>
 
 
 void printint(int v) {
    printf("printint: %d\n", v);
 }
 */
 //注释中写出需要的函数和头文件
 //头文件被include之后所以的C语言元素都会被放入C这个虚拟包中
 import "C"
 //import C需要单独一行 不能和别的包同时导入
 func main() {
    v := 42
    C.printint(C.int(v)) //Go是强语言类型 所有CGO中的参数都要和声明的变量一样 传递前需要用C中的转化函数进行转化
 }

CGO中的强转问题

Image

  • 在CGO生成的_cgo_export.h头文件中会对Go语言中的字符串、切片、字典、接口和管道等特有的数据类型生成对应的C语言类型,不过只有字符串和切片在CGO中有一定的使用价值,因为只有二者可以在GO调用C语言函数时马上使用,而CGO并未针对其他的类型提供相关的辅助函数。

  • C语言的结构体、联合、枚举类型不能作为匿名成员被嵌入到Go的结构体中。在Go中我们可以通过C.struct_xxx来访问C语言中定义的struct xxx结构体类型,结构体的内存布局按照C语言的通用对齐规则,在32位环境中按照32位对齐,64位中按照64位对齐

  •  package main
     
     //结构体
     /*
     struct A{
        int i;
        float f;
     };
     */
     import "C"
     
     
     import "fmt"
     
     
     func main() {
        var a C.struct_A
        fmt.Print(a.i)
        fmt.Print(a.f)
     }
  • 字符串 数组的转换

  •  CGO的虚拟包提供了以下一组函数 用于Go语言和C语言之间数组和字符串的双向转换 (C语言中没有切片)
     
     func C.CString(string) *C.char
     
     func C.CBytes([]byte) unsafe.Pointer
     
     func C.GoString(*C.char) string
     
     func C.GoStringN(*C.char, C.int) string
     
     
     func C.GoBytes(unsafe.Pointer, C.int) []byte
     
     //该组辅助函数都是以克隆的方式运行 当Go中的字符串和切片向C转换时,克隆的内存由C中的malloc分配,最后以free函数释放
     当C向GO转换时 克隆的内存由GO分配管理
  • 指针的转换

  •  众所周知C语言最亮眼的一点就是指针的自由转换和指针运算
     cgo存在的一个目的就是打破Go语言对指针的禁止 在Go中不同指针的转换是比较严格的 同时也不允许对指针进行运算 仅能进行取地址和解引用
     
     var p *X
     var q *Y
     
     
     q = (*Y)(unsafe.Pointer(p)) // *X => *Y
     p = (*X)(unsafe.Pointer(q)) // *Y => *X
     
     
     为了实现X类型到Y类型指针的转换 我们需要借助unsafe.Pointer作为中间桥梁实现不同类型指针之间的转换 unsafe.Pointer指针类型类似于C语言中的void*类型

    Image

函数调用

 package main
 
 
 /*
 static int add(int a,int b) {
    return a+b;
 }
 */
 import "C"
 import "fmt"
 
 
 func main() {
    fmt.Println(C.add(1,2))
    fmt.Println(C.add(3,9))
 }
 
 //对于启用CGO特性的程序 CGO会构造一个虚拟的C包 通过这个虚拟的C包可以调用C语言函数
 
 /*
 static int div(int a, int b) {
    return a/b;
 }
 */import "C"import "fmt"
 
 
 func main() {
    v := C.div(6, 3)
    fmt.Println(v)
 }
 
 //对于有返回值的C函数 我们可以正常获取返回值
 
 //CGO也针对<errno.h>标准库的errno宏做的特殊支持;在CGO中调用C函数时如果有两个返回值 那么第二个返回值一定是errno错误类型 (在GO中就是err类型)
 
 //CGO还有一个强大的特性:在GO函数导出为C语言函数 这样的话我们可以定义好C语言接口 然后通过GO语言实现 (需要export指令)
 
 package main
 
 
 //void SayHello(char* s);
 
 import "C"
 
 
 import (
    "fmt"
 )
 
 
 func main() {
    C.SayHello(C.CString("Hello, World\n"))
 }
 
 
 //export SayHellofunc SayHello(s *C.char) {
    fmt.Print(C.GoString(s))
 }

CGO中间件、内存模型和静态、动态库

Image

CGO内存模型:CGO在二进制接口层面实现了互通,但我们要注意因两种语言的内存模型的差异而可能引起的问题,如果在CGO处理的跨语言函数调用时涉及了指针的传递,则可能出现GO和C共享某一段内存的场景。这二者最大的差异就是:C语言的内存在分配之后就是稳定的,而GO因为函数栈的动态手速哦可能导致栈中内存地址的移动

静态库和动态库:

CGO在使用C/C++资源的时候一般有三种形式:

直接使用源码 链接静态库和链接动态库

  • 直接使用源码:在import"C"之前的注释部分包含C代码

  • 静态库和动态库的方式比较类似 是在LDFLAGS选项中指定要链接的库方式链接

 

posted @   安妮的心动录  阅读(208)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示