CGO 是什么?
CGO 是 GO 语言外面的一个个性,CGO 属于 GOLANG 的高级用法,次要是通过应用 GOLANG 调用 CLANG 实现的程序库
应用
咱们能够应用
import "C"
来应用 CGO 这个个性
一个最简略的 CGO 应用
package main
//#include <stdio.h>
import "C"
func main(){C.puts(C.CString("Hello, Cgo\n"))
}
import "C"
的上方能够写须要导入的库 C 库,须要正文起来,CGO 会将此处的正文内容当做 C 的代码,被称为 序言(preamble)
上述代码的性能解释
应用 CGO 包的
C.CString
函数将 Go 语言字符串转为 C 语言字符串最初调用 CGO 包的
C.puts
函数向规范输入窗口打印转换后的 C 字符串
应用 go build -x main.go
编译一下
加上 -x
能够打印出编译过程中执行的指令
# go build -x main.go
WORK=/tmp/go-build594331603
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=/root/.cache/go-build/fb/fbb37eeb6735cb453f6d92e2e3f46f14d9dceb5baa1cdd10aae11d1d47d60e55-d
packagefile runtime/cgo=/usr/local/go/pkg/linux_amd64/runtime/cgo.a
packagefile syscall=/usr/local/go/pkg/linux_amd64/syscall.a
packagefile runtime=/usr/local/go/pkg/linux_amd64/runtime.a
packagefile errors=/usr/local/go/pkg/linux_amd64/errors.a
packagefile internal/bytealg=/usr/local/go/pkg/linux_amd64/internal/bytealg.a
packagefile internal/oserror=/usr/local/go/pkg/linux_amd64/internal/oserror.a
packagefile internal/race=/usr/local/go/pkg/linux_amd64/internal/race.a
packagefile internal/unsafeheader=/usr/local/go/pkg/linux_amd64/internal/unsafeheader.a
packagefile sync=/usr/local/go/pkg/linux_amd64/sync.a
packagefile internal/cpu=/usr/local/go/pkg/linux_amd64/internal/cpu.a
packagefile runtime/internal/atomic=/usr/local/go/pkg/linux_amd64/runtime/internal/atomic.a
packagefile runtime/internal/math=/usr/local/go/pkg/linux_amd64/runtime/internal/math.a
packagefile runtime/internal/sys=/usr/local/go/pkg/linux_amd64/runtime/internal/sys.a
packagefile internal/reflectlite=/usr/local/go/pkg/linux_amd64/internal/reflectlite.a
packagefile sync/atomic=/usr/local/go/pkg/linux_amd64/sync/atomic.a
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=Vv0to6CWqbWf5_KTN66F/K36AEO-x4qJ_LJbz5wgG/HVbBbLSaW0sTSwlN8TzN/Vv0to6CWqbWf5_KTN66F -extld=gcc /root/.cache/go-build/fb/fbb37eeb6735cb453f6d92e2e3f46f14d9dceb5baa1cdd10aae11d1d47d60e55-d
/usr/local/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out main
rm -r $WORK/b001/
尝试本人写一个 C 函数,让 GO 来调用他
Go 语言环境中调用这个 SayHello
函数
package main
/*
#include <stdio.h>
static void SayHello(const char* s) {puts(s);
}
*/
import "C"
func main(){C.SayHello(C.CString("hello xiaomotong study cgo\n"))
}
尝试本人写一个 C 文件,而后 GO 中进行导入和调用
xmtC.h
void SayHi(const char * str);
xmtC.c
(必须是同级目录下的 .c 文件,cgo 应用 go build 编译的时候,会默认在同级目录下找 .c
文件进行编译,如果咱们是须要将 C 文件做成动态库 或者 动静库的形式,那么就不要将 C 的源码文件放到同级目录下了,防止重名)
#include <stdio.h>
#include "xmtC.h"
void SayHi(const char * str){puts(str);
}
main.go
package main
//void SayHi(const char * str);
import "C"
func main(){C.SayHi(C.CString("hello xiaomotong study cgo\n"))
}
间接运行 go build
进行编译,运行可执行程序即可
# go build
# ls
cgo main.go xmtC.c
# ./cgo
hello xiaomotong study cgo
通过面向 C 语言接口的编程技术,咱们不仅仅解放了函数的实现者,同时也简化的函数的使用者。当初咱们能够将 SayHi 当作一个规范库的函数应用(和 puts 函数的应用形式相似)
咱们也能够在 go 文件中写成这个样子
package main
//#include <xmtC.h>
import "C"
func main(){C.SayHi(C.CString("hello xiaomotong study cgo\n"))
}
合并 C 和 GO 的代码
在 Go1.10
中 CGO 新减少了一个 _GoString_
预约义的 C 语言类型,用来示意 Go 语言字符串
// +build go1.10
package main
//void SayHi(_GoString_ s);
import "C"
import ("fmt")
func main() {C.SayHi("hello xiaomotong study cgo\n")
}
//export SayHi
func SayHi(s string) {fmt.Print(s)
}
上述代码的具体执行逻辑程序是这样的:
CGO 环境
应用 CGO 须要肯定的环境环境反对
- linux 下 须要有
gcc/g++
的编译环境 - windows 下须要有 MinGW 工具
- 须要把 GO 的环境变量
CGO_ENABLED
置位 1
上述的例子中,咱们有几个须要留神的点:
import "C"
语句不能和其余的 import 语句放在一起,须要独自一行搁置- 上述咱们在 GO 外面传递的值,例如
C.CString("hello xiaomotong study cgo\n")
是调用了 C 的虚构包,将字符串转换成 C 的字符串传入进去 -
Go 是强类型语言
所以 cgo 中传递的参数类型必须与申明的类型完全一致,而且传递前必须用”C”中的转化函数转换成对应的 C 类型,不能间接传入 Go 中类型的变量
通过虚构的 C 包导入的 C 语言符号并 不须要是大写字母结尾,它们不受 Go 语言的导出规则束缚
cgo 用法
咱们能够应用 #cgo
语句设置编译阶段和链接阶段的相干参数
- 编译阶段的参数
次要用于定义相干宏和指定头文件检索门路
- 链接阶段的参数
次要是指定库文件检索门路和要链接的库文件
例如咱们能够这样
// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
// #cgo LDFLAGS: -L/usr/local/lib -lpng
// #include <png.h>
import "C"
CFLAGS
- -DPNG_DEBUG
定义宏 PNG_DEBUG,设置为 1
- -I
定义头文件的检索目录是 ./include
LDFLAGS
- -L
指定链接时库文件检索目录,能够通过写 ${SRCDIR}
来示意以后包的绝对路径
- -l
指定链接时须要的库,此处是 png 库
条件编译 build tag
就是在咱们 go build
的时候,增加一些条件参数,当然这个条件参数在对应的文件中是须要有的,
例如上述咱们应用 Go1.10
的时候,就在文件中增加了 // +build go1.10
咱们能够这样用:
go build -tags="debug"
go build -tags="debug test"
go build -tags="linux,386"
go build 的时候加上 -tags
参数,若有多个咱们能够一起写,用 空格
距离,示意 或
,用 逗号
距离示意 与
GO 和 C 数据类型互相转换
cgo 官网提供了如下的数据类型转换:
C 语言类型 | CGO 类型 | Go 语言类型 |
---|---|---|
char | C.char | byte |
singed char | C.schar | int8 |
unsigned char | C.uchar | uint8 |
short | C.short | int16 |
unsigned short | C.ushort | uint16 |
int | C.int | int32 |
unsigned int | C.uint | uint32 |
long | C.long | int32 |
unsigned long | C.ulong | uint32 |
long long int | C.longlong | int64 |
unsigned long long int | C.ulonglong | uint64 |
float | C.float | float32 |
double | C.double | float64 |
size_t | C.size_t | uint |
须要留神 3 个点:
- CGO 中,C 语言的
int
和long
类型都是对应 4 个字节的内存大小,size_t
类型能够当作 Go 语言uint
无符号整数类型看待 - CGO 中,C 语言的
int
固定为 4 字节的大小,GO 语言的int
和uint
却在 32 位和 64 位零碎下别离对应 4 个字节和 8 个字节大小 - 例如数据类型两头有空格,
unsigned int
不能间接通过C.unsigned int
拜访,能够应用typedef
关键字提供一个规定的类型命名,这样更利于在 CGO 中拜访
字符串和切片类型
CGO 生成的 _cgo_export.h
头文件中有 GO 外面字符串,切片,通道,字典,接口等数据类型对应的示意形式,然而咱们个别应用有价值的就是字符串和切片了
因为 CGO 没有提供其余数据类型的辅助函数
typedef struct {const char *p; GoInt n;} GoString;
咱们导出函数的时候能够这样写:
应用 _GoString_
预约义类型,这样写能够升高在 cgo 代码中可能对 _cgo_export.h
头文件产生的循环依赖的危险
_GoString_
是 Go1.10 针对 Go 专门加的字符
extern void helloString(_GoString_ p0);
咱们能够应用官网提供的函数 计算字符串的长度 和 获取字符串的地址:
size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);
struct,union,enum
GO 语言中拜访 C 语言的 struct,union,enum,能够查看下表的对应关系
C 语言 | GO 语言 |
---|---|
struct xx | C.struct_xx |
union xx | C.union_xx |
enum xx | C.enum_xx |
对于构造体 struct
构造体的内存布局依照 C 语言的通用对齐规定
在 32 位 Go 语言环境 C 语言构造体也依照 32 位对齐规定,在 64 位 Go 语言环境依照 64 位的对齐规定
对于指定了非凡对齐规定的构造体,无奈在 CGO 中拜访
GO 中能够这样拜访 C 的构造体
package main
/*
struct struct_TEST {
int i;
float f;
};
*/
import "C"
import "fmt"
func main() {
var a C.struct_TEST
a.i = 1
a.f = 2
fmt.Println(a.i)
fmt.Println(a.f)
}
须要留神如下 2 个大点:
- 构造体成员的名字和 GO 中关键字的名字一样咋解决
例如上述构造体成员名字是这样的
struct struct_TEST {
int type;
float f;
};
那么咱们拜访 type 的时候,能够这样拜访 a._type
即可
若构造体是这样的呢?
struct struct_TEST {
int type;
float _type;
};
咱们拜访的时候依然是这样拜访,a._type
,不过理论拜访到的是 float _type;
,通过 GO 就没有方法拜访到 int type;
GO 中也无法访问 C 中的 零长数组 和 位字段,例如
struct struct_TEST {
int size: 10; // 位字段无法访问
float arr[]; // 零长的数组无法访问};
- 在 C 语言中,无奈间接拜访 Go 语言定义的构造体类型
对于枚举 enum
枚举类型底层对应 int
类型,反对正数类型的值,咱们能够间接应用 C.xx 来进行拜访
例如枚举类型为:
enum TEST {
ONE,
TWO,
};
应用这个类型咱们能够用 c C.enum_TEST
给这个变量复制的时候,咱们能够这样做:c = C.ONE
对于联合体 union
Go 语言中并不反对 C 语言联结类型,它们会被转为对应大小的字节数组
例如
union B1 {
int i;
float f;
};
union B1
会被转换成为 4 个字节大小的 字节数组 [4]uint8
GO 中操作联合体变量有 3 种形式:
- 在 C 语言中定义辅助函数
- Go 语言的
encoding/binary
手工解码成员(须要留神大端小端问题) - 应用
unsafe
包强制转型为对应类型
举个例子
package main
/*
#include <stdint.h>
union TEST {
int i;
float f;
};
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
var b C.union_TEST
*(*C.int)(unsafe.Pointer(&b)) = 1
fmt.Println("b.i:", *(*C.int)(unsafe.Pointer(&b)))
fmt.Println("b.f:", *(*C.float)(unsafe.Pointer(&b)))
}
咱们读取和写入联合体变量的时候,应用 unsafe
包性能是最好的,通过unsafe
获取指针,而后转成对应的数据类型的指针即可
参考资料:
GO 高级编程
欢送点赞,关注,珍藏
敌人们,你的反对和激励,是我保持分享,提高质量的能源
好了,本次就到这里
技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。
我是 小魔童哪吒,欢送点赞关注珍藏,下次见~