咱们好,我是煎鱼

最近因为各种古怪的原因,接触到了 Go 特色之一 CGO。这方面的相关内容也相对少一些,给咱们抛砖引玉。

快速上手 Go CGO,掌握在 Go 里写 C!

究竟许多跨言语调用,仍是会依赖 CGO 这个特性。希望咱们在真实要用时有个前置知识垫肚子。

CGO 是什么

CGO 就是 C 和 Go,两个编程言语。指的是能够创建调用 C 代码的 Go 包。对照着 Go 代码中的 “C”:

package main
import "C"
func main() {}

一旦程序中呈现 import "C",则意味着敞开 CGO 特性。在进行 go build 等阶段时,将会调用 C 编译器(通常是 gcc 或 clang)。

CGO 对应的环境变量是 CGO_ENABLED,设置为 1 则敞开 CGO,为 0 则封闭 CGO。

编译指令如下:

CGO_ENABLED=0 go build -o hellojy main.go

当然,对于默许值。该环境变量值为 1,C 编译器也是运用 gcc。咱们能够经过 go env 看到:

快速上手 Go CGO,掌握在 Go 里写 C!

一旦封闭就会影响 CGO 编译。需求特别留心,交叉编译时会默许封闭 CGO。

CGO 快速上手

最小 Demo

先来一个 CGO 的 Go 例子:

package main
//#include <stdio.h>
import "C"
func main() {
	s := C.CString("hello world.")
	C.puts(s)
}

运转 go run main.go,输出结果:

hello world.

声明 C 注解

假如你没有了解过 CGO,看到上面的例子,可能会有好几个疑问。

首先是 include:

//#include <stdio.h>
import "C"

import "C" 咱们懂,是导入 C 的伪包。 前面的注解是什么?

无论是:

//#include <stdio.h>

又或是:

/*
#include <stdio.h>
#include <stdlib.h>
*/

实际上这是导入 C 前的注解,注解内容能够包括任何 C 代码,例如:函数、变量的声明定义、库引证等。(该注解要紧挨导入句子

回到 Demo 自身,假如咱们去掉 //#include <stdio.h>,再运转会呈现如下报错:

# command-line-arguments
./main.go:7:2: could not determine kind of name for C.puts

去掉后,句子 C.puts(s) 将无法运转。

实际上 stdio.h 的全称是:standard input output.header(标准输入输出头文件)。该文件大都是些输入输出函数的声明,引证了这库,就能运用 C 的 puts 办法。

其他同理,你在注解中声明、定义的东西,均能够在 Go 代码中经过 C 这个伪包来引证和调用。

其次像是 CString 办法,归于在 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 类型对照

Go 官方有提供一份根底类型的对照表,咱们能够参照来运用和了解。

如下:

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

注意事项

运用 CGO,除了会带来必定的性能损耗外。需求特别注意的是:内存泄露。因为 Go 是带废物收回机制的编程言语,而运用了 C 后,需求手动的办理内存。

仍是这个 Demo:

package main
//#include <stdio.h>
import "C"
func main() {
	s := C.CString("hello world.")
	C.puts(s)
}

假如这是一个常驻进程,也没有任何开释动作。用 C.CString 办法所请求的变量 s 就会泄露。

因此与 “C” 相关的变量创建,需求进行手动的内存办理。正确的代码如下:

/*
#include <stdio.h>
#include <stdlib.h>
*/
import "C"
import (
	"unsafe"
)
func main() {
	b := C.CString("hello world.")
	C.puts(b)
	C.free(unsafe.Pointer(b))
}

需求调用 C.free 办法进行主动的内存开释。假如该程序天然完毕,也会主动收回。

总结

在今天这篇文章中,咱们介绍了 Go 言语中 CGO 的根底知识和快速入门。整体上,只要习惯了写法,CGO 的用法就不算太费事。

需求特别注意手动内存办理、性能损耗等多方面的制约。后续咱们也会继续深化 CGO 方面的内容。

文章继续更新,能够微信搜【脑子进煎鱼了】阅览,本文 GitHub github.com/eddycjy/blo… 已收录,学习 Go 言语能够看 Go 学习地图和路线,欢迎 Star 催更。

Go 图书系列

  • Go 言语入门系列:初探 Go 项目实战
  • Go 言语编程之旅:深化用 Go 做项目
  • Go 言语规划哲学:了解 Go 的为什么和规划思考
  • Go 言语进阶之旅:进一步深化 Go 源码

推荐阅览

  • Go 在信创这一块会输给 Java,想不通。。。
  • Go 错误处理:100+ 提案全部被回绝,为何现阶段仍用 if err != nil?
  • Go 为什么不像 Rust 用 ?!做错误处理?