在 Go 里可以通过 CGO 调用 C 函数,最简单的写法就是直接在 import "C" 前一行写 C 函数:

package main

/*
int sum(int a, int b) { return a + b; }
*/
import "C"
import "fmt"

func main() {
    fmt.Println(C.sum(C.int(1), C.int(2)))
}

但,如果是那么简单的函数,也不需要用到 CGO,直接 Go 实现就行了,真要用到 CGO 了,大概率就是你需要调用一些比较复杂的 C 代码,可以用下面两种方式实现:

静态编译

上面的例子其实就是静态编译,编译器会把 C 代码跟 Go 代码一起编译进可执行文件里。

像刚刚说的,实际项目开发中要调用 C 函数不会那么简单,一般都是会有一个或多个 .c 文件,然后 Go 怎么调用呢?其实不难,只需要在 Go 里把对应的头文件(如果没有,就需要我们自己定义一个了)引入进来就行:

继续拿上面的 sum() 举例,首先我们需要一个 sum.h

int sum(int a, int b);

然后需要有 sum.c

#include "sum.h"

int sum(int a, int b)
{
    return a + b;
}

sum.hsum.c 放在跟调用它的 Go 代码文件同一个目录,然后 Go 代码里只需要:

package main

/*
#include "sum.h"
*/
import "C"
import "fmt"

func main() {
    fmt.Println(C.sum(C.int(1), C.int(2)))
}

执行 CGO_ENABLED=1 go build -o cgo_example . 就可以把 C 代码静态编译到可执行程序里去了。

当然,上面这种写法可能不太优雅,你可以把 CGO 相关的 C 和 Go 代码放到一个目录(例如 /cgo)里去,然后对入口做封装:

package cgo

/*
#include "sum.h"
*/
import "C"

func Sum(a, b int) int {
    return int(C.sum(C.int(a), C.int(b)))
}

动态链接

静态链接有一个要求就是你必须有 C 代码,如果你只有头文件但没有 C 代码、或者不方便拷贝 C 代码到项目目录里,那么你可以通过动态链接的形式来调用 C 函数,只需要加上 cgo LDFLAGS 即可,具体实现:

把前面的 sum.hsum.c 放到 ./dylib 里,然后编译成动态链接库:

gcc -shared -o libsum.so sum.c

然后在 Go 代码里:

package main

/*
#cgo LDFLAGS: -L./dylib/ -lsum
#include "sum.h"
*/
import "C"
import "fmt"

func main() {
    fmt.Println(Sum(1, 2))
}

func Sum(a, b int) int {
    return int(C.sum(C.int(a), C.int(b)))
}

然后执行 CGO_ENABLED=1 go build -o cgo_example . 编译,最后把 cgo_examplelibsum.so 放到同一个目录即可运行。

上面这个例子不够优雅,假如我们要调用某些开源库的函数,它在我们本地开发环境可能是 /usr/local/lib/libsum.dylib,而到了服务器上可能是 /usr/lib/libsum.so,那么在代码里 hardcode 就会出现问题。

其实可以不用在代码里写 #cgo ... 那行,我们可以通过 CGO_LDFLAGS 环境变量来指定要在哪找动态链接库(-L./dylib/)和链接库的名称(-lsum),这样就可以根据机器实际安装情况来编译了

标签: go

添加新评论