7

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

 3 years ago
source link: https://yanbin.blog/go-invoke-c-dylib-linux/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

2021-08-16 — Yanbin

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

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

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

#ifndef __ADD_H__
#define __ADD_H__
char* Add(char* src, int n);
#endif

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

#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, 内容

#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 的代码如下:

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK