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.hxxx.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]
相关推荐
hlsd#1 小时前
go 集成go-redis 缓存操作
redis·缓存·golang
qq_1728055914 小时前
GIN 反向代理功能
后端·golang·go
__AtYou__15 小时前
Golang | Leetcode Golang题解之第535题TinyURL的加密与解密
leetcode·golang·题解
kevin_tech20 小时前
Go API 多种响应的规范化处理和简化策略
开发语言·后端·golang·状态模式
幺零九零零1 天前
【Golang】sql.Null* 类型使用(处理空值和零值)
数据库·sql·golang
cookies_s_s1 天前
Golang--DOS命令、变量、基本数据类型、标识符
golang
__AtYou__1 天前
Golang | Leetcode Golang题解之第541题反转字符串II
leetcode·golang·题解
flying robot1 天前
Go的JSON转化
golang
幺零九零零1 天前
【Golang】validator库的使用
开发语言·后端·golang
海绵宝宝de派小星1 天前
Go:接口和反射
开发语言·后端·golang