七叶笔记 » golang编程 » 大白话 golang 教程-27-调用 C 语言函数

大白话 golang 教程-27-调用 C 语言函数

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

 package main

// cgo enabled default

import "C"

func main() {}  

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

 package main 

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

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

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

 int puts(const char *str)  

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

 // 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,上面的例子完整的代码是:

 //#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。

 str := C.GoString(cstr)
fmt.Println(str)  

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

 // 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 来实现:

 // 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 运行了。

 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” 时,编译会产生 C 和 Go 之间的中介代码,执行 go tool cgo main.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.

2.

本章节的代码

相关文章