Go语言与C/C++进行交互

本节将分别为大家讲解 Go语言是如何与 C/C++ 进行交互的。

C语言进行交互

工具 cgo 提供了对 FFI(外部函数接口)的支持,能够使用 Go语言代码安全地调用 C语言库,可以访问 cgo 文档主页:https://golang.google.cn/cmd/cgo/。cgo 会替代 Go 编译器来产生可以组合在同一个包中的 Go 和 C 代码。

在实际开发中一般使用 cgo 创建单独的 C 代码包。如果想要在 Go 程序中使用 cgo,则必须在单独的一行使用 import "C" 来导入,一般来说你可能还需要 import "unsafe" 。

然后,可以在 import "C" 之前使用注释(单行或多行注释均可)的形式导入 C语言库(甚至有效的 C语言代码),它们之间没有空行,例如:
// #include <stdio.h>
// #include <stdlib.h>
import "C"
名称 "C" 并不属于标准库的一部分,这只是 cgo 集成的一个特殊名称用于引用 C 的命名空间。在这个命名空间里所包含的 C 类型都可以被使用,例如 C.uint 、C.long 等等,还有 libc 中的函数 C.random() 等也可以被调用。

当想要使用某个类型作为 C 中函数的参数时,必须将其转换为 C 中的类型,反之亦然,例如:
var i int
C.uint(i) // 从 Go 中的 int 转换为 C 中的无符号 int
int(C.random()) // 从 C 中 random() 函数返回的 long 转换为 Go 中的 int
【示例 1】下面的 2 个 Go 函数 Random() 和 Seed() 分别调用了 C 中的 C.random() 和 C.srandom()。
package rand
// #include <stdlib.h>
import "C"
func Random() int {
    return int(C.random())
}
func Seed(i int) {
    C.srandom(C.uint(i))
}
C语言当中并没有明确的字符串类型,如果你想要将一个 string 类型的变量从 Go 转换到 C 时,可以使用 C.CString(s);同样,可以使用 C.GoString(cs) 从 C 转换到 Go 中的 string 类型。

Go 的内存管理机制无法管理通过 C 代码分配的内存。开发人员需要通过手动调用 C.free 来释放变量的内存:

defer C.free(unsafe.Pointer(Cvariable))

这一行最好紧跟在使用 C 代码创建某个变量之后,这样就不会忘记释放内存了。

【示例 2】下面的代码展示了如何使用 cgo 创建变量、使用并释放其内存:
package print
// #include <stdio.h>
// #include <stdlib.h>
import "C"
import "unsafe"
func Print(s string) {
    cs := C.CString(s)
    defer C.free(unsafe.Pointer(cs))
    C.fputs(cs, (*C.FILE)(C.stdout))
}

构建 cgo 包

除了使用变量 GOFILES 之外,还需要使用变量 CGOFILES 来列出需要使用 cgo 编译的文件列表。例如,可以使用包含以下内容的 Makefile 文件来编译,可以使用 gomake 或 make:
include $(GOROOT)/src/Make.inc
TARG=rand
CGOFILES=\
c1.go\
include $(GOROOT)/src/Make.pkg

与 C++ 进行交互

SWIG(简化封装器和接口生成器)支持在 Linux 系统下使用 Go语言代码调用 C 或者 C++ 代码。这里有一些使用 SWIG 的注意事项:
  • 编写需要封装的库的 SWIG 接口。
  • SWIG 会产生 C 的存根函数。
  • 这些库可以使用 cgo 来调用。
  • 相关的 Go 文件也可以被自动生成。

这类接口支持方法重载、多重继承以及使用 Go 代码实现 C++ 的抽象类。

目前使用 SWIG 存在的一个问题是它无法支持所有的 C++ 库,比如说它就无法解析 TObject.h。