いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-220 (標準ライブラリのpluginパッケージで簡易プラグイン処理)

概要

知っていると少し便利かもしれないTipsです。

Goには標準ライブラリで plugin というパッケージが用意されていて、シンプルにプラグイン処理が出来ます。

pkg.go.dev

プラグインとして利用するための条件は以下です。

  • mainパッケージに公開したい関数を定義しておく
  • go build -buildmode=plugin でビルドする

ビルドすると so ファイルが生成されます。なので、windowsは残念ながらサポートされていません。

サンプル

実際の構造を見ていただいたほうがわかりやすいかもしれません。

以下にアップしています。

こんな感じのファイル構成にしています。

gitpod /workspace/try-golang (master) $ tree examples/singleapp/stdlib_plugin_pkg/
examples/singleapp/stdlib_plugin_pkg/
├── lib
│   ├── lib.go
│   └── pkg
│       └── strs
│           └── upper.go
├── main.go
└── Makefile

3 directories, 4 files

libディレクトリの下がプラグインとしてビルドするものです。

それを利用する側が直下の main.go 。

lib/pkg/strs/upper.go

package strs

import (
    "strings"
)

func Upper(s string) string {
    return strings.ToUpper(s)
}

lib/lib.go

// プラグインとして利用する処理は main パッケージに属している必要がある

package main

import (
    "fmt"

    "github.com/devlights/try-golang/examples/singleapp/stdlib_plugin_pkg/lib/pkg/strs"
)

func Fn(message string) {
    fmt.Println(strs.Upper(message))
}

main.go

package main

import (
    "log"
    "plugin"
)

func exitOnErr(err error) {
    if err != nil {
        log.Fatalln(err)
    }
}

func main() {
    var (
        plg *plugin.Plugin
        sym plugin.Symbol
        err error
    )

    // プラグインをオープンして
    plg, err = plugin.Open("lib/lib.so")
    exitOnErr(err)

    // シンボルを取得
    sym, err = plg.Lookup("Fn")
    exitOnErr(err)

    // シンボルは 対象となる値 (変数とか関数) へのポインタとなっている
    // 関数の場合は sym.(func()) で利用できるが、変数の場合は *(sym.(*int)) のようにする必要がある点に注意
    if fn, ok := sym.(func(string)); ok {
        fn("hello world")
    }
}

Taskfile.yml

go-task のタスクファイルです。Makeの代わり。

version: '3'

tasks:
  build:
    cmds:
      - cd lib ; go build -buildmode=plugin
      - go build -o app
  clean:
    cmds:
      - rm -f ./app
      - find . -name "*.so" | xargs -I{} rm -f {}
  run:
    deps: [ build ]
    cmds:
      - ./app
  file-tree:
    cmds:
      - find . -print | sed -e "s;[^/]*/;|____;g;s;____|; |;g"

ビルドしてみる

ビルドすると以下のように生成物が出来上がります。

gitpod /workspace/try-golang (master) $ task -d examples/singleapp/stdlib_plugin_pkg/ build
task: [build] cd lib ; go build -buildmode=plugin
task: [build] go build -o app
gitpod /workspace/try-golang (master) $ tree examples/singleapp/stdlib_plugin_pkg/
examples/singleapp/stdlib_plugin_pkg/
├── app
├── lib
│   ├── lib.go
│   ├── lib.so
│   └── pkg
│       └── strs
│           └── upper.go
├── main.go
├── Makefile
└── Taskfile.yml

3 directories, 7 files

lib/lib.so と app が出来上がりました。

上の main.go の中で、lib/lib.so をプラグインとして読み込むようにしているので、実行すると

gitpod /workspace/try-golang (master) $ task -d examples/singleapp/stdlib_plugin_pkg/ run
task: [build] cd lib ; go build -buildmode=plugin
task: [build] go build -o app
task: [run] ./app
HELLO WORLD

ちゃんとプラグイン側の処理が呼ばれて結果を返してくれていますね。

参考情報


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。