Go 调用 C 写的动态库完整例子(Linux版)

总有那么一些老的,或高效的库是用 C/C++ 实现的,于是在其他语言中如果使用动态共享库就成了个问题。Java 要调用动态库需要用 JNI, 更快捷的话可使用第三方包装好的 JNI 调用库。在 Java 中要映射 C/C++ 的类型麻烦些,因为 Java 没有指针类型,所以从这方面来讲 Go 调用动态库幸许会更简单些。

下面我们自己在 Linux 下做一个动态库(.so 文件  - Shared Object),然在用 Go 来使用它。本文所用的操作系统为 Ubuntu20.04, 以 gcc  作为编译器。动态库的生成过程参考自 Linux动态库生成与使用指南

我们用动态库实现一个拼接字符串与整数的函数,首选是 add.h  文件中的函数声明

然后是 add 函数的实现 add.c 文件,内容为

接着用命令编译生成动态库,在 Linux 下的文件名是  libadd.so

$ gcc -fPIC -shared -o libadd.so add.c

会在当前目录下生成 libadd.so 文件, 在 Linux 下可用 nm -D libadd.so  查看其中的方法

现在用一个 C 语言代码来使用它,代码文件为 test.c, 内容

链接动态库生成可执行文件

$ gcc test.c -L . -ladd -o test

-L .表示搜索要链接的库文件时包含当前目录
-ladd  表示要链接动态库 libadd.so
-o test 生成可执行文件 test

运行 test

由于 libadd.so 是动态库,也就是执行期需要加载它,假如直接执行 test 会怎么样呢?

$ ./test
./test: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory

找不到动态库 libadd.so,  Linux 通过 ldconfig 的指示在某些目录中(如 /lib, /user/lib) 搜索动态库。更简单的办法是用 LD_LIBRARY_PATH 环境变量,如

$ LD_LIBRARY_PATH=. ./test
giter8

至此,动态库 libadd.so 准备好了,并且用 test 验证了它是可用的,接下来就在 Go 语言中使用该动态库的函数。

以下是成功的例子,测试完之后发现很简单,可是过程中碰到许多的问题。后面会列出所遇到见的问题

假设项目目录为 /home/vagrant/testgo (用的 Vagrant 启动的 Ubuntu 20.04 进行本文中的测试),目录结构如下

testgo
├── lib
│     └── libadd.so
└── src
        ├── add.h
        └── main.go

main.go 的代码如下:

通过注释代码来告诉 Go 编译器从哪里引入头文件与加载动态库. 本例中 *.h 和 *.go 文件在同一个目录的情况下, #cgo CFLAGS: -I. 可不写。

CFLAGS: -I 和 LDFLAGS: -L 都是相对于源文件 main.go 的位置

执行,命令行进到 /home/vagrant/testgo 目录

~/testgo$ go run src/main.go
Hello c value: go2021

成功调用 C 实现的 add 函数

下面列出一些问题

import "C" 要紧挨着 /*...*/ 注释块,如果写成

/*
#cgo ...
*/

import "C"

会出现错误

# command-line-arguments
src/main.go:15:10: could not determine kind of name for C.add

import "C" 要独占一行, 试图同时引入其他的库,如 import ("C"; "fmt") 也会报上面同样的错误

加载不到头文件的错误很明显,#include "add.h" 时会告诉你该文件不存在,如果没有加载到正确的头文件调用 C.Add() 函数时就会报错

# command-line-arguments
src/main.go:13:10: could not determine kind of name for C.Add

还有一个关键是能否加载到动态库 libadd.so, 参考了网上一些例子,如果把第五行改为

#cgo LDFLAGS: -L../lib -ladd

执行时会出错

/tmp/go-build3845117109/b001/exe/main: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
exit status 127

但如果设置了环境变量 LD_LIBRARY_PATH=/home/vagrant/testgo/lib 也能让它跑起来

LD_LIBRARY_PATH=/home/vagrant/testgo/lib go run src/main.go
Hello c value: go2021

链接:

  1. 全面总结:Golang 调用 C/C++, 例子式教程
  2. golang 学习 (10): 使用go语言调用c语言的so动态库
  3. GoLang 调用 .so 文件 (go plugin 调用 go 生成的动态库)

本文链接 https://yanbin.blog/go-invoke-c-dylib-linux/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Categories:
Subscribe
Notify of
guest

2 Comments
Inline Feedbacks
View all comments
trackback

[…] Go 调用 C 写的动态库完整例子(Linux版) 弄完了 Go 语言如何调用动态库,又开始琢磨起 Python 怎么调用动态库,首先仍然是以前一篇中的 C 实现为例,C 函数为原型为 char * Add(char* src, int n), 由于用符号直接定位函数,所以无需 C 的头文件。本文仍然是以 Linux 平台为例,GCC 编译为动态库 so 文件。并实验了两个例子,一个为基本的类型,char* 和  int, 再一个就是在 C 中使用到了结构体指针和无类型指针(void*) 时,如何在 Python 进行调用。 […]

trackback

[…] Go 调用 C 写的动态库完整例子(Linux版),是在编译器告诉用 /* #cgo ...*/ 的方式去加载动态库 […]