logo

罕见的 Go CLI 命令 (2/5):如何构建共享库

Go 提供了丰富的 CLI 命令工具,使我们不仅可以编写常规代码,还可以编写供其他 Go 程序调用的插件,以及供其他语言调用的库。本文主要从三个方面学习如何利用 Go CLI 命令构建共享库 (shared libraries):

1. Go 中如何创建插件

Go 的插件(plugin)是一种机制,允许在运行时动态加载和执行编译好的代码。插件的目的是为了实现应用程序的可扩展性和灵活性。通过使用插件,开发人员可以将应用程序的功能模块化,使其能够在不重新编译整个应用程序的情况下,动态地加载新的功能或模块。

这个理论看起来挺唬人,但要说写个 demo 也简单:

package main

import "fmt"

func ThingsToDo() {
  fmt.Println("Code in plugin...")
}

上面的代码就可以作为一个 Go 插件,要成为插件,有几点需要说明:

所以 上面的代码 可以使用 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 中如何创建静态库

静态库和共享库是两种常见的库文件形式。先了解一下什么是静态库:

这个理论看起来挺唬人,但要说写个 demo 也简单:

package main

import (
  "C"
  "fmt"
)

func main() {}

//export Hello
func Hello() {
  fmt.Println("Hello from Go Static Library")
}

上面的代码有几点需要说明:

代码目录内 使用 go build -buildmode c-archive hello.go 编译代码,会生成两个文件: hello.ahello.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.ahello.h 文件,再次执行 a.out 也依然没有什么问题。

3. Go 中如何创建共享库

共享库也称动态链接库,了解一下:

静态库在编译时被链接到目标程序中,而共享库在程序运行时被动态加载。

这个理论看起来挺唬人,但要说写个 demo 也简单:

package main

import (
  "C"
  "fmt"
)

func main() {}

//export Hello
func Hello() {
  fmt.Println("Hello from Go Shared Library")
}

这个代码和上面的代码没有什么区别 (唯一的区别是 fmt.PrintlnStatic 改成了 Shared),使用 go build -buildmode c-shared hello.go 编译代码,会生成两个文件:hellohello.h

依然是同样的 C 代码,使用 gcc hello.c ./hello 编译,会生成一个 a.out 文件,执行此文件得到输出:

./a.out
# Hello from Go Shared Library

但由于这次我们构建的是共享库,删除 hello 文件之后再次执行 a.out 则会报错。

4. 总结

本文的目的只是指引作用,也许我们永远不会用到这些知识,但当某一天需要时,知道这个知识点的存在再根据需求进一步探索,应该会更轻松一些。