罕见的 Go CLI 命令 (2/5):如何构建共享库
Go 提供了丰富的 CLI 命令工具,使我们不仅可以编写常规代码,还可以编写供其他 Go 程序调用的插件,以及供其他语言调用的库。本文主要从三个方面学习如何利用 Go CLI 命令构建共享库 (shared libraries):
- 如何创建 Go 插件 (用于 Go 代码之间,不支持 Windows)
- 如何创建通用库 (用于与其它语言,如 C,Python)
- 静态库 (static)
- 共享库 (shared)
1. Go 中如何创建插件
Go 的插件(plugin)是一种机制,允许在运行时动态加载和执行编译好的代码。插件的目的是为了实现应用程序的可扩展性和灵活性。通过使用插件,开发人员可以将应用程序的功能模块化,使其能够在不重新编译整个应用程序的情况下,动态地加载新的功能或模块。
这个理论看起来挺唬人,但要说写个 demo 也简单:
package main
import "fmt"
func ThingsToDo() {
fmt.Println("Code in plugin...")
}
上面的代码就可以作为一个 Go 插件,要成为插件,有几点需要说明:
- 包名需要是
main
- 不需要有
main()
函数,但需要有导出函数 (即大写字母开头) 可供调用 - 需要使用构建标志将其构建为插件代码
go help build
有一个标志-buildmode
,其介绍为:build mode to use. See ‘go help buildmode’ for more.go help buildmode
有介绍-buildmode=plugin
:Build the listed main packages, plus all packages that they import, into a Go plugin. Packages not named main are ignored.
所以 上面的代码 可以使用 go build -buildmode=plugin ./plugin.go
编译为插件,结果是在同一目录内生成一个 plugin.so
的文件。
创建插件之后看一下该如何使用,我们创建另一个 demo 程序,代码如下:
func main() {
path := flag.String("plugin", "", "plugin to execute")
flag.Parse()
if *path == "" {
log.Fatal("path to plugin not specified")
}
p, _err := plugin.Open(*path)
symbol, _err := p.Lookup("ThingsToDo")
thingsToDo, ok := symbol.(func())
if !ok {
log.Fatalf("could not find function 'ThingsToDo' in plugin")
}
thingsToDo()
log.Println("Did the things")
}
上面的代码并不复杂:
- 运行程序时传入插件路径
- 打开插件,查找函数
- 类型断言成功后,执行函数
使用 go run . -plugin ../plugin/plugin.so
运行上面的代码,得到输出:
Code in plugin...
2024/05/05 21:36:02 Did the things
2. Go 中如何创建静态库
静态库和共享库是两种常见的库文件形式。先了解一下什么是静态库:
- 静态库是在编译时被链接到目标程序中的库文件。
- 静态库的代码在编译时被复制到目标程序中,因此目标程序在运行时不再依赖于静态库。
- 静态库的文件扩展名通常为
.a
(在 Windows 上为.lib
)。 - 静态库的优点是在程序运行时不需要外部依赖,但缺点是会增加目标程序的体积。
这个理论看起来挺唬人,但要说写个 demo 也简单:
package main
import (
"C"
"fmt"
)
func main() {}
//export Hello
func Hello() {
fmt.Println("Hello from Go Static Library")
}
上面的代码有几点需要说明:
import "C"
必须要有//export Hello
也是必须的:固定格式是//export
,Hello 是要导出的函数名- 因为我们不需要
main
函数,但没有它又无法编译,所以保留一个空的main
函数
在 代码目录内 使用 go build -buildmode c-archive hello.go
编译代码,会生成两个文件: hello.a
,hello.h
。
在同一目录内写个简单的 C 代码:
// hello.c
#include "hello.h"
int main(void) {
Hello();
return 0;
}
运行 gcc hello.c ./hello.a -lpthread
编译 C 文件,生成一个 a.out
可执行文件,执行这个文件得到输出:
./a.out
# Hello from Go Static Library
由于我们生成的是静态库,此时删除 hello.a
,hello.h
文件,再次执行 a.out
也依然没有什么问题。
3. Go 中如何创建共享库
共享库也称动态链接库,了解一下:
- 共享库是在程序运行时被动态加载到内存中的库文件。
- 共享库的代码在程序运行时被共享,多个程序可以共享同一个共享库的实例。
- 共享库的文件扩展名通常为
.so
(在 Windows 上为.dll
)。 - 共享库的优点是可以减小目标程序的体积,但缺点是在运行时需要确保共享库的可用性。
静态库在编译时被链接到目标程序中,而共享库在程序运行时被动态加载。
这个理论看起来挺唬人,但要说写个 demo 也简单:
package main
import (
"C"
"fmt"
)
func main() {}
//export Hello
func Hello() {
fmt.Println("Hello from Go Shared Library")
}
这个代码和上面的代码没有什么区别 (唯一的区别是 fmt.Println
中 Static 改成了 Shared),使用 go build -buildmode c-shared hello.go
编译代码,会生成两个文件:hello
,hello.h
。
依然是同样的 C 代码,使用 gcc hello.c ./hello
编译,会生成一个 a.out
文件,执行此文件得到输出:
./a.out
# Hello from Go Shared Library
但由于这次我们构建的是共享库,删除 hello
文件之后再次执行 a.out
则会报错。
4. 总结
本文的目的只是指引作用,也许我们永远不会用到这些知识,但当某一天需要时,知道这个知识点的存在再根据需求进一步探索,应该会更轻松一些。
- 命令
go help buildmode
- 代码