初识CGO
CGO:C/C++经过几十年的发展,已经积累了海量的软件资产,它们很多久经考验而且性能足够优化。GO必须要能够站在C/C++这个巨人的肩膀之上。C语言作为一个通用工具,很多库会选择提供一个C兼容的API,然后用其他不同的编程语言实现。GO通过自带的一个CGO工具来支持C语言函数调用,同时我们可以用GO语言导出C动态库接口给其他语言使用
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中的强转问题
-
在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*类型
函数调用
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中间件、内存模型和静态、动态库
CGO内存模型:CGO在二进制接口层面实现了互通,但我们要注意因两种语言的内存模型的差异而可能引起的问题,如果在CGO处理的跨语言函数调用时涉及了指针的传递,则可能出现GO和C共享某一段内存的场景。这二者最大的差异就是:C语言的内存在分配之后就是稳定的,而GO因为函数栈的动态手速哦可能导致栈中内存地址的移动
静态库和动态库:
CGO在使用C/C++资源的时候一般有三种形式:
直接使用源码 链接静态库和链接动态库
-
直接使用源码:在import"C"之前的注释部分包含C代码
-
静态库和动态库的方式比较类似 是在LDFLAGS选项中指定要链接的库方式链接
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现