背景
cgo,在使用场景上使用 c编写的代码。运行c的代码,得到执行的结果。
介绍
首先,http://golang.org/cmd/cgo是主要的cgo文档。
在http://golang.org/doc/articles/c_go_cgo.html上也有一篇很好的介绍文章。
基础
如果一个Go源文件导入了” C “,那么它正在使用 cgo 。Go文件将访问在导入“C”行之前的注释中出现的任何内容,并将链接到其他Go文件中的所有其他cgo注释,以及构建过程中包含的所有C文件。 注意,cgo注释和import语句之间必须没有空行。 要访问源自C端的符号,请使用包名C。也就是说,如果您想从Go代码中调用C函数printf(),则需要编写C.printf()。由于像printf这样的变量参数方法还不被支持,我们将它包装在C方法“myprint”中:
package main
/*
#include <stdio.h>
#include <stdlib.h>
void myprint(char* s) {
printf("%s\n", s);
}
*/
import "C"
import "unsafe"
func Example() {
cs := C.CString("Hello from stdio\n")
C.myprint(cs)
C.free(unsafe.Pointer(cs))
}
func main(){
Example()
}
从C调用Go函数
使用cgo从Go代码调用C代码,可以同时调用顶级Go函数和函数变量。
全局函数
Go通过使用一个特殊的//导出注释使它的函数对C代码可用。 注意:如果你在使用exports,你不能在前言中定义任何C函数。 例如,有两个文件,foo.c和foo.go foo.go 如下
package gocallback
import "fmt"
/*
#include <stdio.h>
extern void ACFunction();
*/
import "C"
//export AGoFunction
func AGoFunction() {
fmt.Println("AGoFunction()")
}
func Example() {
C.ACFunction()
}
foo.c 如下
#include "_cgo_export.h"
void ACFunction() {
printf("ACFunction()\n");
AGoFunction();
}
最后我这边编译失败…报错如下;后续想办法解决一下
# command-line-arguments
Undefined symbols for architecture x86_64:
"_ACFunction", referenced from:
__cgo_5c1dc23bd62c_Cfunc_ACFunction in _x002.o
(maybe you meant: __cgo_5c1dc23bd62c_Cfunc_ACFunction)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
函数变量
下面的代码显示了从C代码调用Go回调的示例。由于指针传递规则,Go代码不能直接将函数值传递给c,而需要使用间接传递。
本例使用了一个带有互斥锁的注册表,但是还有许多其他方法可以将可以传递给C的值映射到Go函数。
package main
import (
"fmt"
"sync"
)
/*
extern void go_callback_int(int foo, int p1);
// normally you will have to define function or variables
// in another separate C file to avoid the multiple definition
// errors, however, using "static inline" is a nice workaround
// for simple functions like this one.
static inline void CallMyFunction(int foo) {
go_callback_int(foo, 5);
}
*/
import "C"
//export go_callback_int
func go_callback_int(foo C.int, p1 C.int) {
fn := lookup(int(foo))
fn(p1)
}
func MyCallback(x C.int) {
fmt.Println("callback with", x)
}
func Example() {
i := register(MyCallback)
C.CallMyFunction(C.int(i))
unregister(i)
}
var mu sync.Mutex
var index int
var fns = make(map[int]func(C.int))
func register(fn func(C.int)) int {
mu.Lock()
defer mu.Unlock()
index++
for fns[index] != nil {
index++
}
fns[index] = fn
return index
}
func lookup(i int) func(C.int) {
mu.Lock()
defer mu.Unlock()
return fns[i]
}
func unregister(i int) {
mu.Lock()
defer mu.Unlock()
delete(fns, i)
}
func main(){
Example()
}
执行结果:
callback with 5
回调函数指针
C代码可以调用带有显式名称的导出Go函数。但是,如果c程序需要函数指针,就必须编写网关函数。这是因为我们不能把Go函数的地址交给C代码,因为cgo工具会在C中生成一个应该被调用的存根。下面的例子展示了如何与需要给定类型的函数指针的C代码集成。
为了更好的了解,在 这个文件 下面创建 $GOPATH/src/ccallbacks/. 下面的代码,并且编译和运行他们。
goprog.go
package main
/*
#cgo CFLAGS: -I .
#cgo LDFLAGS: -L . -lclibrary
#include "clibrary.h"
int callOnMeGo_cgo(int in); // Forward declaration.
*/
import "C"
import (
"fmt"
"unsafe"
)
//export callOnMeGo
func callOnMeGo(in int) int {
fmt.Printf("Go.callOnMeGo(): called with arg = %d\n", in)
return in + 1
}
func main() {
fmt.Printf("Go.main(): calling C function with callback to us\n")
C.some_c_func((C.callback_fcn)(unsafe.Pointer(C.callOnMeGo_cgo)))
}
cfuncs.go
package main
/*
#include <stdio.h>
// The gateway function
int callOnMeGo_cgo(int in)
{
printf("C.callOnMeGo_cgo(): called with arg = %d\n", in);
int callOnMeGo(int);
return callOnMeGo(in);
}
*/
import "C"
clibrary.h
#ifndef CLIBRARY_H
#define CLIBRARY_H
typedef int (*callback_fcn)(int);
void some_c_func(callback_fcn);
#endif
clibrary.c
#include <stdio.h>
#include "clibrary.h"
void some_c_func(callback_fcn callback)
{
int arg = 2;
printf("C.some_c_func(): calling callback with arg = %d\n", arg);
int response = callback(2);
printf("C.some_c_func(): callback responded with %d\n", response);
}
拥有上述代码后,还得执行相关的操作;如下
$ gcc -c clibrary.c
$ ar cru libclibrary.a clibrary.o
$ go build
$ ./ccallbacks
Go.main(): calling C function with callback to us
C.some_c_func(): calling callback with arg = 2
C.callOnMeGo_cgo(): called with arg = 2
Go.callOnMeGo(): called with arg = 2
C.some_c_func(): callback responded with 3
Go strings 和 C strings
Go 字符串 和C字符串是不同的。 Go字符串是长度和指向字符串中第一个字符的指针的组合。C字符串只是指向第一个字符的指针,以null字符’\0’的第一个实例结束。 Go以以下三种功能的形式提供了从一个地方到另一个地方的途径:
func C.CString(goString string) *C.char
func C.GoString(cString *C.char) string
func C.GoStringN(cString *C.char, length C.int) string
需要记住的一件重要事情是C.CString()将分配一个合适长度的新字符串,并返回它。这意味着C字符串不会被垃圾收集,而是由您来释放它。下面是一种标准的方法。
// #include <stdlib.h>
import "C"
import "unsafe"
...
var cmsg *C.char = C.CString("hi")
defer C.free(unsafe.Pointer(cmsg))
// do something with the C string
当然,您不需要使用defer来调用C.free()。您可以在任何时候释放C字符串,但确保释放这个是你的工作。
将C数组转换为Go切片
C数组通常要么以空结尾,要么在其他地方保留长度。 Go提供以下函数来从C数组中创建一个新的Go字节片:
func C.GoBytes(cArray unsafe.Pointer, length C.int) []byte
要创建一个由C数组支持的Go切片(而不复制原始数据),需要在运行时获取这个长度,然后使用类型转换到一个非常大的数组的指针,然后将其切片到你想要的长度
import "C"
import "unsafe"
...
var theCArray *C.YourType = C.getTheArray()
length := C.getTheArrayLength()
slice := (*[1 << 28]C.YourType)(unsafe.Pointer(theCArray))[:length:length]
重要的是要记住,Go垃圾收集器不会与这个数据交互,如果它从C端释放出来,使用这个片的任何Go代码的行为都是不确定的。
常见的陷阱
结构体对齐的问题
因为Go不支持打包结构(例如,最大对齐为1字节的结构),你不能在Go中使用打包的C结构。即使你的程序通过了编译,它也不会做你想做的事情。要使用它,您必须以字节数组/片的形式读写该结构体。
另一个问题是,有些类型的对齐要求低于Go中的对应类型,如果该类型恰好在C中对齐,但在Go规则中没有对齐,那么该结构就无法在Go中表示
struct T {
uint32_t pad;
complex float x;
};
Go的complex64有一个8字节的对齐,而as C只有4字节(因为C在内部将复杂的float作为一个结构体{float real;浮动图像放大;},不是一个基本类型),这个T结构体没有Go表示。在这种情况下,如果你控制结构的布局,移动复杂的浮动,使它也对齐到8字节会更好,如果你不愿意移动它,使用这个形式将强制它对齐到8字节(并浪费4字节):
struct T {
uint32_t pad;
__attribute__((align(8))) complex float x;
};
然而,如果你不能控制struct布局,你将不得不为该struct定义访问器C函数,因为cgo无法将该struct转换为等效的Go结构。
//export 和定义的问题
如果Go源文件使用了任何//export指令,那么注释中的C代码可能只包含声明(extern int f();),而不包含定义(int f() {return 1;}或int n;)。
注意:对于序言中定义的小函数,可以使用静态内联技巧来绕过这一限制。
Windows
为了在Windows上使用cgo,你还需要首先安装一个gcc编译器(例如,mingw-w64),并在使用cgo编译之前在你的PATH环境变量中拥有gcc.exe(等等)。
环境变量
Go os.Getenv()没有看到C.setenv()设置的变量。变量设置不是共享的。
tests 测试
_test.go 文件 不能使用 cgo。