golang 动态库 (buildmode)

目录

  • [1. golang 动态库](#1. golang 动态库)
  • [2. Golang 生成 C 动态库 `.so` 和静态库 `.a`](#2. Golang 生成 C 动态库 .so 和静态库 .a)
    • [2.1. 源代码](#2.1. 源代码)
    • [2.2. 编译](#2.2. 编译)
    • [2.3. C](#2.3. C)
    • [2.4. 执行](#2.4. 执行)
    • [2.5. 如何生成静态库](#2.5. 如何生成静态库)
    • [2.6. Go 调用 C 库](#2.6. Go 调用 C 库)
      • [2.6.1. 源代码](#2.6.1. 源代码)
  • [3. golang 语言使用动态库、调用动态链接库](#3. golang 语言使用动态库、调用动态链接库)
    • [3.1. Go 插件系统](#3.1. Go 插件系统)
    • [3.2. 动态加载的优劣](#3.2. 动态加载的优劣)
    • [3.3. Go 的插件系统: Plugin](#3.3. Go 的插件系统: Plugin)
    • [3.4. 插件开发原则](#3.4. 插件开发原则)
      • [3.4.1. 插件独立](#3.4.1. 插件独立)
      • [3.4.2. 使用接口类型作为边界](#3.4.2. 使用接口类型作为边界)
      • [3.4.3. Unix 模块化原则](#3.4.3. Unix 模块化原则)
      • [3.4.4. 版本控制](#3.4.4. 版本控制)
    • [3.5. 插件开发示例](#3.5. 插件开发示例)
      • [3.5.1. 编写插件](#3.5.1. 编写插件)
      • [3.5.2. 使用插件](#3.5.2. 使用插件)

1. golang 动态库

2. Golang 生成 C 动态库 .so 和静态库 .a

2.1. 源代码

Go 生成 C 动态库 .so 和静态库 .a

go 复制代码
package main
 
import "C"
import "fmt"
 
//export hello
func hello(){
    fmt.Println("hello world")
}
//export add
func add(a,b int) int {
    return a+b
}

func main(){
}

注意: 生成 C 可调用的 so 时, Go 源代码需要以下几个注意。

  1. 必须导入 "C" 包
  2. 必须在可外部调用的函数前加上 【//export 函数名】的注释
  3. 必须是 main 包, 切含有 main 函数, main 函数可以什么都不干

2.2. 编译

先要安装 go 的标准库

sh 复制代码
go install -buildmode=shared -linkshared std

编译共享库

sh 复制代码
go build -buildmode=c-shared -o so 库文件名 自己的项目

然后当前目录就会出现 xxx.h 和 xxx.so 文件

2.3. C

go 复制代码
#include<stdio.h>
#include"libtest.h" //生成的头文件
 
void main(){
	hello();
	printf("\n2+3=%d\n",add(2,3));
}

编译:

sh 复制代码
gcc goso.c  -L ./ -ltest -o goso

2.4. 执行

由于是共享库, 那么运行时就需要加载需要的库。在 linux 中默认库的路径为/usr/lib 或者/usr/lib64 。如果想将自己所在的文件夹也添加到库搜索目录中去。那么有两种方式:

  1. 修改配置文件, 将自己的目录添加到库搜索目录列表中去。/etc/ld.so.conf 然后执行 ldconfig
  2. 修改环境变量, 临时改变库搜索路径。 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH: 自己的目录

2.5. 如何生成静态库

只需要将 buildmode 改为 c-archive 即可。然后编译时将静态库参与编译即可。

2.6. Go 调用 C 库

2.6.1. 源代码

注意:

需要使用到 cgo 工具

  1. 直接在 import "C"之前添加一个注释。 然后使用 C 语法添加库的头文件。
  2. 针对共享库由于是操作系统管理程序运行加载的共享库, 所以可以不用管, 只需要将 so 库放入对应的目录即可。针对静态库, 那么就要在代码中多添加一行, 告诉编译器编译时需要连接的库。// #cgo LDFLAGS: -L ./ -lfoo
go 复制代码
package main
 
//#cgo LDFLAGS: -L ./ -lfoo     使用静态库时需要添加
//#include"xxx.h"
import "C"
xxxx
func main(){
    C.xxx(xxx)
}

3. golang 语言使用动态库、调用动态链接库

3.1. Go 插件系统

通过使用插件在运行时扩展程序的功能, 而无需重新编译程序, 这是一个很常见的功能需求, 特别是在模块化设计的程序里面, 比如 Nginx 的模块系统。 在 C/C++中通过使用动态库的方式可以实现动态加载, 但是 Go 直到 1.8 官方才开始支持, 下面将介绍 Go 如何基于动态链接库来实现动态加载。

3.2. 动态加载的优劣

优点:

  • 动态加载, 也称热加载, 每次升级时不用重新编译整个工程, 重新部署服务, 而是添加插件时进行动态更新。这对于很多比较重型的服务来说非常重要。

缺点:

  • 带来一定的安全风险, 如果一些非法模块被注入如何防范
  • 给系统带来一定的不稳定的因素, 如果模块有问题, 没有经过良好的测试, 容易导致服务崩溃
  • 为版本管理带来了难题, 特别是在微服务的今天, 同一个服务, 加载了不同的插件, 应该怎么管理版本, 插件版本应该如何管理

因此请慎重考虑, 是使用动态插件还是在源码里面进行插件化。

3.3. Go 的插件系统: Plugin

从 1.8 版开始, 官方提供了这种插件化的手段: plugin. 此功能使程序员可以使用动态链接库构建松散耦合的模块化程序, 可以在运行时动态加载和绑定。

Go 插件是使用 -buildmode = plugin 标记编译的一个包, 用于生成一个共享对象 (.so) 库文件。 Go 包中的导出的函数和变量被公开为 ELF 符号, 可以使用 plugin 包在运行时查找并绑定 ELF 符号。Go 编译器能够使用 build flag -buildmode = c-shared 创建 C 风格的动态共享库。

1.8 版本插件功能只能在 Linux 上使用。 1.10 也可以在 Mac 上运行。

下面将介绍使用 Go 插件系统创建模块化软件的一些开发原则, 并提供一个功能齐全的示例。

3.4. 插件开发原则

使用 Go 插件创建模块化程序需要遵循与常规 Go 软件包一样严格的软件实践。然而, 插件引入了新的设计问题, 因为它们的解耦性质被放大了。因此我们在设计可插拔系统时, 有一些原则需要关注:

3.4.1. 插件独立

应该将插件视为与其他组件分离的独立组件。这允许插件独立于他们的消费者, 并拥有自己的开发和部署生命周期。注意插件的可用性很重要, 因为它有肯能为整个系统带来不稳定的因素, 因此系统必须为插件集成提供一个简单的封装层, 插件开发人员将系统视为黑盒, 不作为所提供的合约以外的假设, 从而保证插件自身的可用性。

3.4.2. 使用接口类型作为边界

Go 插件可以导出任何类型的包函数和变量。您可以设计插件来将其功能解耦为一组松散的函数。缺点是您必须单独查找和绑定每个函数符号。

然而, 更为简单的方法是使用接口类型。创建导出功能的接口提供了统一简洁的交互, 并具有清晰的功能划分。解析到接口的符号将提供对该功能的整个方法集的访问, 而不仅仅是一个方法。

3.4.3. Unix 模块化原则

插件代码应该设计成只关注一个功能点。

3.4.4. 版本控制

插件是不透明而独立的实体, 应该进行版本控制, 以向用户提示其支持的功能。这里的一个建议是在命名共享对象文件时使用语义版本控制。例如, 上面的文件编译插件可以命名为 eng.so.1.0.0。

3.5. 插件开发示例

我以我遇到的一个实际需求为例, 在开发物联网接入组件的时候, 需要动态支持物解析, 下面就开发一个物解析的插件系统。

下面是项目结构, parser.go 是接口规约, main.go 是主程序, plugins 存放多个插件包

sh 复制代码
├── main.go
├── parser.go
└── plugins
    ├── car
    │   └── car.go
    └── phone
        └── phone.go

3.5.1. 编写插件

  • 编写主程序接口规约: main.go
go 复制代码
package main
 
// Parser use to parse things
type Parser interface {
byte) (meta map[string]string, data map[string]float64, err error)
}
  • 根据接口规约编写插件: car.go
go 复制代码
package main
 
type car string
 
func (c *car) Parse([]byte) (meta map[string]string, data map[string]float64, err error) {
map[string]string{"key1": "a"}
map[string]float64{"key1": 1}
 
return meta, data, nil
}
 
var Car car
  • 根据接口规约编写插件: phone.go
go 复制代码
package main
 
type phone string
 
func (p *phone) Parse([]byte) (meta map[string]string, data map[string]float64, err error) {
map[string]string{"key1": "b"}
map[string]float64{"key1": 2}
 
return meta, data, nil
}
 
var Phone phone
  • 编译插件插件写完后将在 plugins 目录下编译插件:
go 复制代码
$ cd plugins
$ go build -buildmode=plugin -o car.so car/car.go
$ go build -buildmode=plugin -o phone.so phone/phone.go

最终在 plugins 目录下会生成好我们编译好的插件:

sh 复制代码
$ ls *.so
car.so   phone.so

3.5.2. 使用插件

插件的使用很简单, 大概步骤如下:

  • 用 plugin.Open() 打开插件文件
  • 用 plguin.Lookup("Export-Variable-Name") 查找导出的符号"Car"或者"Phone"。 请注意, 符号名称与插件模块中定义的变量名称相匹配
  • 使用该变量

主程序使用插件: main.go

go 复制代码
package main
 
import (
"fmt"
"plugin"
)
 
// Parser use to parse things
type Parser interface {
byte) (meta map[string]string, data map[string]float64, err error)
}
 
func pa() {
"./plugins/car.so")
if err != nil {
panic(err)
	}
 
"Car")
if err != nil {
panic(err)
	}
 
	p, ok := car.(Parser)
if ok {
byte("a"))
if err != nil {
panic(err)
		}
"meta: %v, data: %v \n", meta, data)
	}
}
 
func pb() {
"./plugins/phone.so")
if err != nil {
panic(err)
	}
 
"Phone")
if err != nil {
panic(err)
	}
 
	p, ok := phone.(Parser)
if ok {
byte("a"))
"meta: %v, data: %v \n", meta, data)
	}
}
 
func main() {
	pa()
	pb()
}

测试是否正常运行:

sh 复制代码
$ go run main.go
meta: map[key1:a], data: map[key1:1]
meta: map[key1:b], data: map[key1:2]
相关推荐
不是万能的豆学长9 小时前
排查Go服务40天后的内存泄漏问题
开发语言·后端·golang·go
tachibana210 小时前
Go 语言 Map 的底层实现原理
开发语言·后端·golang·make
tachibana211 小时前
Go语言里slice是怎么扩容的?
后端·golang·go·切片
西瓜很甜哟11 小时前
golang实现大文件分片上传
后端·golang·html
tachibana21 天前
使用for range 的时候,它的地址会发生变化吗?
开发语言·后端·golang·go·语法
王中阳Go1 天前
Go 后端转 AI:非科班怎么补?一张 3 列路线表(可保存)
开发语言·人工智能·后端·python·golang·大语言模型
for_ever_love__1 天前
LangChain Agentic RAG:让 Agent 自己决定要不要检索、检索几次
人工智能·golang·langchain·rag·langgraph·agentic·自适应检索
谢亮_vipxieliang1 天前
Go gRPC基础与实战
开发语言·后端·golang
徐小黑ACG1 天前
Golang goroutine、通道、锁
开发语言·后端·golang
FfHUCisI2 天前
Go 性能分析工具 pprof:CPU、Heap 与 Goroutine 实战
数据库·golang