Go 语言通过 cgo 提供了与 C 代码交互的能力,使得开发者能够在 Go 程序中直接调用 C 语言的函数和库。无论是嵌入 C 代码,还是链接 C 动态库,cgo 都能让 Go 程序与 C 语言代码紧密结合,发挥 C 的高性能和 Go 的便利性。

在本篇博客中,我们将逐步展示如何通过 cgo 在 Go 中调用 C 代码,包括:

  1. 在 Go 中嵌入 C 代码
  2. 调用外部 C 动态库(.so 文件)

1. 使用 cgo 调用 C 代码

cgo 是 Go 的一个工具,它允许在 Go 程序中嵌入 C 代码或者调用 C 库。cgo 通过特殊的注释语法将 C 代码插入到 Go 代码中,从而实现 C 与 Go 的互操作性。

  • 嵌入 C 代码:你可以将 C 代码直接嵌入到 Go 文件中,Go 编译器会在编译过程中处理这些 C 代码。
  • 调用 C 库:你可以通过 #cgo 指令链接外部的 C 动态库(如 .so 文件),并在 Go 中调用其中的函数。

2. 在 Go 中嵌入 C 代码

2.1 示例:简单的 C 函数调用

假设我们有一个简单的 C 函数,它用于计算两个整数的和。我们将通过 cgo 把 C 代码嵌入到 Go 中,并调用该函数。

libmath.c(C 代码):

#include <stdio.h>

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

我们将把这个 C 函数集成到 Go 程序中:

main.go(Go 代码):

package main

/*
#include "libmath.c"
*/
import "C"
import "fmt"

func main() {
    // 调用 C 中的 add 函数
    result := C.add(2, 3)
    fmt.Println("Result from C add function:", result)
}

在这里:

  • #include "libmath.c":将 C 代码嵌入到 Go 文件中。
  • C.add(2, 3):通过 C 包调用 C 中的 add 函数。

2.2 编译和运行

通过 go run 命令运行该 Go 程序,Go 会自动编译并链接嵌入的 C 代码:

$ go run main.go

# 输出结果应为:
Result from C add function: 5

3. 在 Go 中调用 C 动态库

除了将 C 代码嵌入到 Go 中外,cgo 还可以用于调用外部的 C 动态库(.so 文件)。假设我们已经有一个 C 动态库,并希望在 Go 程序中调用其中的函数。

3.1 创建 C 动态库

我们首先创建一个简单的 C 动态库 libmath.so,该库包含一个 add 函数:

libmath.c(C 代码):

#include <stdio.h>

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

编译为动态库:

$ gcc -shared -o libmath.so -fPIC libmath.c

这将生成 libmath.so 文件,这个库将被 Go 程序加载。

3.2 使用 cgo 调用 C 动态库

在 Go 程序中,我们使用 #cgo 指令来告诉 Go 程序链接 libmath.so 动态库,并调用其中的 add 函数。

main.go(Go 代码):

/*
#cgo LDFLAGS: -L. -lmath
#include <stdio.h>

// 声明 C 函数
extern int add(int a, int b);
*/
import "C"
import "fmt"

func main() {
    // 调用 C 动态库中的 add 函数
    result := C.add(2, 3)
    fmt.Println("Result from C add function:", result)
}

在这个 Go 程序中:

  • #cgo LDFLAGS: -L. -lmath:告诉 Go 链接 libmath.so 动态库。
  • extern int add(int a, int b);:声明 C 函数 add

3.3 编译和运行

与之前一样,通过 go run 命令运行该 Go 程序,Go 会自动编译并链接嵌入的 C 代码:

$ go run main.go

# 输出结果应为:
Result from C add function: 5

4. 可能出现的问题

在执行编译过程中,你遇到的错误 cannot open shared object file: No such file or directory,通常表示在运行 Go 程序时,操作系统无法找到你要链接的 C 动态库(如 libadd.so)。这通常是因为动态库文件的位置没有正确设置,或者没有正确配置环境变量以便操作系统能够找到它。

为了让程序正确加载 C 动态库,以下是几种解决方法:

4.1 设置 LD_LIBRARY_PATH 环境变量

LD_LIBRARY_PATH 环境变量告诉操作系统在运行时从哪些目录查找共享库文件。如果你的 libadd.so 库不在标准路径(如 /usr/lib/lib),你需要显式地设置该路径。

4.1.1 在终端设置 LD_LIBRARY_PATH

假设你的 libadd.so 库在当前目录,可以通过以下命令临时设置 LD_LIBRARY_PATH,使得操作系统能够找到它:

$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

此命令将当前目录(.)添加到库查找路径中。然后你可以再次运行 Go 程序:

$ go run main.go

4.1.2 持久化 LD_LIBRARY_PATH 设置

如果你希望每次都能自动设置 LD_LIBRARY_PATH,可以将该命令添加到你的 ~/.bashrc~/.bash_profile 文件中(假设你使用的是 bash shell):

$ echo 'export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH' >> ~/.bashrc
$ source ~/.bashrc

这样,每次启动终端时,系统就会自动将当前目录添加到库查找路径中。

4.2 使用 -rpath 选项

如果你使用 go build 编译程序,你可以使用 -rpath 选项来告诉链接器共享库的查找路径。这是另一种确保共享库能够在运行时被找到的方式。

4.2.1 编译 Go 程序时指定 rpath

你可以在 Go 编译时使用 -ldflags 参数来设置 rpath,指定共享库的路径:

$ go build -o main -ldflags "-rpath=."

这将告诉链接器在当前目录(.)查找共享库。

4.3 将库文件放到标准路径

你也可以将 libadd.so 库文件移动到系统的标准库路径之一,如 /usr/local/lib/lib,然后执行以下命令刷新链接器缓存:

$ sudo ldconfig

这样,操作系统就能够自动找到 libadd.so,并且你不需要设置 LD_LIBRARY_PATH


孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意
腾讯云开发者社区:孟斯特