背景

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。