大白话 golang 教程-27-调用 C 语言函数
CodingByGolang
2021年03月29日 10:09
收录于文集
共29篇

本地的 go build 编译的时候默认是启用 C/C++  支持的,对应的编译开关是 CGO_ENABLED,交叉构建需要使用 CGO_ENABLED=1,最简单的 CGO 程序如下:

代码块
Go
自动换行
复制代码
package main

// cgo enabled default

import "C"

func main() {}
复制成功

没错,import "C&#​34; 可以导入 C 的类型,表明需要调用 C 库,CGO 的特色之一就可以直接在 go 文件里编写 C 语言的模块,编写的 C 代码部分必须在 import "C&#​34; 之上,import 语句之前不能有空行,下面的例子调用了 C 的 puts 函数:

代码块
Go
自动换行
复制代码
package main 

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

func main() {
  C.puts(C.CString("hi, cgo lib"))
}
复制成功

C.puts 调用 puts 函数,它的原型是: 

代码块
clike
自动换行
复制代码
int puts(const char *str)
复制成功

C.CString 是把 go 的 string 转换到 C 的 *char,不过这个转换不会自动释放内存,需用调用 free 释放,下面是 CString 的签名:

代码块
Go
自动换行
复制代码
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char
复制成功

和 C.CString 相似是的 C.CBytes([]byte) unsafe.Pointer 函数用于转换 go 的 []byte 切片,也需要自行 free,上面的例子完整的代码是:

代码块
Go
自动换行
复制代码
//#include <stdio.h>//#include<stdlib.h>import "C"
import "unsafe"

func main() {
  cstr := C.CString("hi, cgo lib")
  C.puts(cstr)
  C.free(unsafe.Pointer(cstr))
}
复制成功

unsafe.Pointer 获得指针的值,传递给 C 的 free 函数,free 函数 stdlib.h 中,所以需要包含。反过来,如果把 C 的 *char 换换成 go 的 string,可以使用 C.GoString 函数,类似的也有 func C.GoBytes(unsafe.Pointer, C.int) []byte。

代码块
Go
自动换行
复制代码
str := C.GoString(cstr)
fmt.Println(str)
复制成功

CGO 会自动构建当前目录下的 C 源文件,所以把可以把上面 C 的部分独立出来,这样可以实现 C 的模块化:

代码块
Go
自动换行
复制代码
// say.h
void Say(const char* s);

// say.c
#include "say.h"
#include <stdio.h>void Say(const char* s) {
    puts(s);
}

// main.go
package main

//#include "say.h"
import "C"

func main() {
  C.Say(C.CString("hi, cgo"))
}
复制成功

请注意,不能使用 go run main.go 来运行了,要使用 go run . 来运行,不仅 go 可以调用 C 模块的函数,C 的函数也可以由 go 来实现:

代码块
Go
自动换行
复制代码
// say.h
void Say(char* s);

// say.go
package main

import "C"

import "fmt"

//export Say
func Say(s *C.char) {
  fmt.Print(C.GoString(s))
}

// main.go
package main

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

func main() {
  C.Say(C.CString("hi, cgo"))
}
复制成功

say.h 头文件申明了 Say 函数的签名,但是它使用 say.go 来实现的函数体,注意两点:

1. // export Say 是必须的,表示把 go 的函数和导出为 C 的函数

2. 和 C 模块化一样,也得使用 go run . 运行否则链接失败

当然也可以把这三个文件合并到一个 go 文件里,又可以使用 go run main.go 运行了。

代码块
Go
自动换行
复制代码
package main

//void Say(char* s);
import "C"

import (
  "fmt"
)

func main() {
  C.Say(C.CString("hi, cgo"))
}

//export Say
func Say(s *C.char) {
  fmt.Println(C.GoString(s))
}
复制成功

CGO 看似实现了 C 和 Go 的直接调用,实施上并非如此,当发现 import "C&#​34; 时,编译会产生 C 和 Go 之间的中介代码,执行 go tool cgo main.go 可以查看到中间代码:

代码块
Go
自动换行
复制代码
27-call-c-module[master*] 🍎 go tool cgo main.go
27-call-c-module[master*] 🍎 tree _obj
_obj
├── _cgo_export.c
├── _cgo_export.h
├── _cgo_flags
├── _cgo_gotypes.go
├── _cgo_main.c
├── main.cgo1.go
└── main.cgo2.c
复制成功

CGO 还支持 # cgo  CFLAGS LDFLAGS 设定编译连接参数,更多 CGO 的知识参考官网:

1. https://golang.org/cmd/cgo/

2. https://blog.golang.org/cgo

本章节的代码 https://github.com/developdeveloper/go-demo/tree/master/27-call-c-module