总有那么一些老的,或高效的库是用 C/C++ 实现的,于是在其他语言中如果使用动态共享库就成了个问题。Java 要调用动态库需要用 JNI, 更快捷的话可使用第三方包装好的 JNI 调用库。在 Java 中要映射 C/C++ 的类型麻烦些,因为 Java 没有指针类型,所以从这方面来讲 Go 调用动态库幸许会更简单些。
下面我们自己在 Linux 下做一个动态库(.so 文件 - Shared Object),然在用 Go 来使用它。本文所用的操作系统为 Ubuntu20.04, 以 gcc 作为编译器。动态库的生成过程参考自 Linux动态库生成与使用指南
我们用动态库实现一个拼接字符串与整数的函数,首选是 add.h
文件中的函数声明
1 2 3 4 5 6 |
#ifndef __ADD_H__ #define __ADD_H__ char* Add(char* src, int n); #endif |
然后是 add 函数的实现 add.c
文件,内容为
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <string.h> #include <stdio.h> #include <stdlib.h> char* Add(char* src, int n) { char str[20]; sprintf(str, "%d", n); char *result = malloc(strlen(src)+strlen(str)+1); strcpy(result, src); strcat(result, str); return result; } |
接着用命令编译生成动态库,在 Linux 下的文件名是 libadd.so
$ gcc -fPIC -shared -o libadd.so add.c
会在当前目录下生成 libadd.so
文件, 在 Linux 下可用 nm -D libadd.so
查看其中的方法
现在用一个 C 语言代码来使用它,代码文件为 test.c
, 内容
1 2 3 4 5 6 7 8 9 |
#include <stdio.h> #include "add.h" int main(int argc, char *argv[]) { char* aa = "giter"; printf("%s\n", Add(aa, 8)); return 0; } |
链接动态库生成可执行文件
$ 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 的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main /* #cgo CFLAGS: -I. // 头文件的位置,相对于源文件是当前目录,所以是 .,头文件在多个目录时写多个 #cgo CFLAGS: ... #cgo LDFLAGS: -L../lib -ladd -Wl,-rpath,lib // 从哪里加载动态库,位置与文件名,-ladd 加载 libadd.so 文件 #include "add.h" */ import "C" import "fmt" func main() { val := C.Add(C.CString("go"), 2021) fmt.Println("Hello c value: ", C.GoString(val)) } |
通过注释代码来告诉 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
链接:
- 全面总结:Golang 调用 C/C++, 例子式教程
- golang 学习 (10): 使用go语言调用c语言的so动态库
- GoLang 调用 .so 文件 (go plugin 调用 go 生成的动态库)
本文链接 https://yanbin.blog/go-invoke-c-dylib-linux/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
[…] Go 调用 C 写的动态库完整例子(Linux版) […]
[…] Go 调用 C 写的动态库完整例子(Linux版) 弄完了 Go 语言如何调用动态库,又开始琢磨起 Python 怎么调用动态库,首先仍然是以前一篇中的 C 实现为例,C 函数为原型为 char * Add(char* src, int n), 由于用符号直接定位函数,所以无需 C 的头文件。本文仍然是以 Linux 平台为例,GCC 编译为动态库 so 文件。并实验了两个例子,一个为基本的类型,char* 和 int, 再一个就是在 C 中使用到了结构体指针和无类型指针(void*) 时,如何在 Python 进行调用。 […]
[…] Go 调用 C 写的动态库完整例子(Linux版),是在编译器告诉用 /* #cgo ...*/ 的方式去加载动态库 […]