Go语言import "C"的用法(新手必看)
如果在 Go 代码中出现 import "C" 语句,则表示使用了 CGO 特性,该语句前的注释是一种特殊语法,其中包含的是正常的 C 语言代码。
注意,要使用 CGO 特性,需要安装 C/C++ 构建工具链,在 macOS 和 Linux 下需要安装 GCC,在 Windows 下需要安装 MinGW 工具链。同时,需要确保环境变量 CGO_ENABLED 被设置为 1,这表示 CGO 处于启用状态。在本地构建时 CGO 默认是启用的,而在交叉构建时 CGO 默认是禁用的。
当 CGO 启用时,还可以在当前目录中包含 C/C++ 对应的源文件。
举个最简单的例子:
需要注意的是,import "C" 导入语句需要单独占一行,不能与其他包一同导入。
向 C 语言函数传递参数也很简单,只需将参数直接转换为对应的 C 语言类型即可。例如,上例中 C.int(v) 用于将 Go 中的 int 类型值强制转换为 C 语言中的 int 类型值,然后调用 C 语言定义的 printint() 函数进行打印。
还要注意的是,Go 是强类型语言,所以 CGO 中传递的参数类型必须与声明的类型完全一致,而且传递前必须用虚拟的 C 包中的转换函数转换为对应的C语言类型,不能直接传入 Go 中的变量类型。此外,通过虚拟的 C 包导入的 C 语言符号不需要以大写字母开头,它们不受 Go 语言的导出规则的约束。
CGO 将当前包引用的 C 语言符号都放到了虚拟的 C 包中,当前包依赖的其他 Go 语言包内部可能也通过 CGO 引入了类似的虚拟的 C 包,但是不同的Go语言包引入的虚拟的 C 包之间的类型是不通用的。这一约束对于要自己构造 CGO 辅助函数可能会产生一定影响。
例如,我们希望在 Go 中定义一个 C 语言字符指针对应的 CChar 类型,并为其增加一个 GoString() 方法,返回 Go 语言字符串:
现在我们可能会想在其他 Go 语言包中使用这个辅助函数:
在 Go 语言中,方法是依附于类型存在的,不同 Go 包中引入的虚拟的 C 包的类型是不同的(main.C 与 cgo_helper.C 类型不同),这导致从它们延伸出来的 Go 类型也是不同的(*main.C.char 与 *cgo_helper.C.char 类型不同),因此,上述代码不能正常工作。
有 Go 语言使用经验的用户可能会建议对参数进行类型转换后再传入,但这种方法也是不可行的,因为 cgo_helper.PrintCString 的参数是其自身包引入的 *C.char 类型,在外部是无法直接获取这个类型的。
换言之,如果一个包在其公开的接口中直接使用了类似 *C.char 等虚拟的 C 包的类型,其他 Go 包将无法直接使用这些类型,除非这个 Go 包也提供了 *C.char 类型的构造函数。出于同样的原因,如果想在 go test 环境直接测试上述 CGO 导出的类型,也会有相同的限制。
注意,要使用 CGO 特性,需要安装 C/C++ 构建工具链,在 macOS 和 Linux 下需要安装 GCC,在 Windows 下需要安装 MinGW 工具链。同时,需要确保环境变量 CGO_ENABLED 被设置为 1,这表示 CGO 处于启用状态。在本地构建时 CGO 默认是启用的,而在交叉构建时 CGO 默认是禁用的。
当 CGO 启用时,还可以在当前目录中包含 C/C++ 对应的源文件。
举个最简单的例子:
package main /* #include <stdio.h> void printint(int v) { printf("printint: %d\n", v); } */ import "C" func main() { v := 42 C.printint(C.int(v)) }这个例子展示了 CGO 的基本使用方法。开头的注释中写了要调用的 C 语言函数和相关的头文件。包含了头文件后,头文件中的所有 C 语言元素都会被加入 "C" 这个虚拟的 C 包中。
需要注意的是,import "C" 导入语句需要单独占一行,不能与其他包一同导入。
向 C 语言函数传递参数也很简单,只需将参数直接转换为对应的 C 语言类型即可。例如,上例中 C.int(v) 用于将 Go 中的 int 类型值强制转换为 C 语言中的 int 类型值,然后调用 C 语言定义的 printint() 函数进行打印。
还要注意的是,Go 是强类型语言,所以 CGO 中传递的参数类型必须与声明的类型完全一致,而且传递前必须用虚拟的 C 包中的转换函数转换为对应的C语言类型,不能直接传入 Go 中的变量类型。此外,通过虚拟的 C 包导入的 C 语言符号不需要以大写字母开头,它们不受 Go 语言的导出规则的约束。
CGO 将当前包引用的 C 语言符号都放到了虚拟的 C 包中,当前包依赖的其他 Go 语言包内部可能也通过 CGO 引入了类似的虚拟的 C 包,但是不同的Go语言包引入的虚拟的 C 包之间的类型是不通用的。这一约束对于要自己构造 CGO 辅助函数可能会产生一定影响。
例如,我们希望在 Go 中定义一个 C 语言字符指针对应的 CChar 类型,并为其增加一个 GoString() 方法,返回 Go 语言字符串:
package cgo_helper // #include <stdio.h> import "C" type CChar C.char func (p *CChar) GoString() string { return C.GoString((*C.char)(p)) } func PrintCString(cs *C.char) { C.puts(cs) }
现在我们可能会想在其他 Go 语言包中使用这个辅助函数:
package main //static const char* cs = "hello"; import "C" import "./cgo_helper" func main() { cgo_helper.PrintCString(C.cs) }然而,这段代码是不能正常工作的,因为当前 main 包引入的 C.cs 变量的类型是当前 main 包的 CGO 构造的虚拟的 C 包下的 *char 类型(具体为 *main.C.char),而 cgo_helper 包引入的 *C.char 类型(具体为 *cgo_helper.C.char)是不同的。
在 Go 语言中,方法是依附于类型存在的,不同 Go 包中引入的虚拟的 C 包的类型是不同的(main.C 与 cgo_helper.C 类型不同),这导致从它们延伸出来的 Go 类型也是不同的(*main.C.char 与 *cgo_helper.C.char 类型不同),因此,上述代码不能正常工作。
有 Go 语言使用经验的用户可能会建议对参数进行类型转换后再传入,但这种方法也是不可行的,因为 cgo_helper.PrintCString 的参数是其自身包引入的 *C.char 类型,在外部是无法直接获取这个类型的。
换言之,如果一个包在其公开的接口中直接使用了类似 *C.char 等虚拟的 C 包的类型,其他 Go 包将无法直接使用这些类型,除非这个 Go 包也提供了 *C.char 类型的构造函数。出于同样的原因,如果想在 go test 环境直接测试上述 CGO 导出的类型,也会有相同的限制。