Go 运行期加载 C 动态库(Linux版)

前面写的一篇 Go 调用 C 写的动态库完整例子(Linux版),是在告诉编译器用 /* #cgo ...*/ 的方式去加载动态库 libadd.so,这让代码丧失了一定的灵活性,比如同样的函数由多个动态库提供了不同的实现。这就需要做到在 Go 程序中可根据不同的输入条件选择不同的动态库实现,大概是
if 条件1 {
    loadLibrary("libadd1.so")
    调用其中的实现函数 add
else if 条件 2 {
    loadLibrary("libadd2.so")
    调用其中的实现函数 add
else {
    loadLibrary("libaddx.so")
    调用其中的实现函数  add
当然上面那样写是不行的,首先每一个动态库应该在程序运行期间只加载一次,定位的函数应该要缓存起来复用。

这时候我们是不能用 #cgo 的方式,像
1/*
2#cgo CFLAGS: -I.
3#cgo LDFLAGS: -L../lib -ladd -Wl,-rpath,lib
4#include "add.h"
5*/
6import "C"
因为它只能在编译构建期加载 libadd.so

还是以一个例子演示动态加载动态库,像 Java 的 System.loadLibrary("libadd") 那样。仍然使用上一篇 Go 调用 C 写的动态库完整例子(Linux版) 中生成的动态库 libadd.so, 导出函数签名为
1char* Add(char* src, int n);
下面是加载 libadd.so 并调用 Add 函数的 Go 代码 test.go
 1package main
 2import "C"
 3import (
 4  "fmt"
 5  "github.com/rainycape/dl"
 6)
 7
 8func main() {
 9  lib, err := dl.Open("./libadd.so", 0)
10  if err != nil {
11    panic(err)
12  }
13  defer lib.Close()
14  var add func(src *C.char, y int) (*C.char)  // 定义函数变量匹配 libadd 中的 Add 函数
15  lib.Sym("Add", &add)                        // 定位 Add 函数地址
16  val := add(C.CString("go"), 2021)
17  fmt.Println("Hello c value: ", C.GoString(val))
18}
因为用到了 github.com/rainycape/dl, 需先安装它,可用 go get github.com/rainycape/dl, 或用 gomod 来下载它。然后
$ go run test.go
Hello c value: go2021
成功。

该项目的最后更新日期是 7 年前 (2015),如果稳定倒无妨,或者可以阅读它的实现代码,主要实现是步骤是
1C.dlopen(library, flag)
2C.dlsym(lib_handle, symbol_name)
Go 1.8 开始的 plugin 也可用来动态的加载动态库,官方的例子是用来加载同样是 Go 写的并用 go build -buildmode=plugin 生成的 *.so 动态库,好像用 plugin 直接加载 纯 C 动态库有些困难

链接:

  1. Golang dlsym Examples
  2. Golang dlopen Examples
  3. plugin_dlopen.go
  4. golang 调用C++动态库 提示fatal: morestack on g0
  5. Go plugin? C library in Go?
  6. How to load .so file in Linux
永久链接 https://yanbin.blog/go-load-c-dynamic-library-programmatically/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。