Featured image of post Golang Plguin

Golang Plguin

Go 動態載入程式

Go 是一個編譯式的語言,也就是說他不像 JS 那樣可以動態執行程式碼。像是 HexoHugo,前者因為是 JS 寫的,因此支援非常豐富的外掛,但後者因為是 Go 寫的,因此在不使用其他直譯式程式語言的情況下,很難製作外掛。
另一個 Go 寫的軟體 ponzu 則是在加入一段程式碼後,重新編譯自己。這麼做解決了外掛的問題,而且又不會失去 Go 的快速,但是就必須保留整個主程式的原始碼,而且也不那麼的「動態」

ponzu 這個軟體兩年沒人維護了,很多東西都怪怪的,超級難編譯

Plugin in Go

在 1.8 版的時候,Go 推出了 Plguin 套件,可以將外掛和主程式分開編譯,如果外掛有更動,不需要重新編譯主程式;主程式也可以動態載入外掛。

外掛

如果要將一個 package 編譯成外掛,首先他的 package 必須是 main,但是裡面的函式 maininit 都不會被執行,只有大寫開頭的變數、型態、函式會被暴露給主程式。
編譯外掛時,需要加上 -buildmode=plguin,這樣 go build 就會將原始碼編譯成 .so(share object)檔,這樣就可以被主程式呼叫。

主程式

主程式要載入外掛前,需要引用 plugin 套件

1
import "plugin"

func Open(path string) (*Plugin, error) 可以載入一個編譯過的外掛,如果重複呼叫這個函式,除了第二次外都會回傳第一次載入的結果,也就是說假如你在很多個 gorutine 中載入同一個外掛,go 會保證他是「安全」的。注意看,path 是個字串,因此你可以動態產生外掛的路徑,不需要寫死。
載入完了之後,用 func (p *Plugin) Lookup(symName string) (Symbol, error) 可以取得外掛暴露出來的變數、函式,因為 symName 是字串,因此這裡也可以動態選擇要的變數。現在你有一個型態是 Symbol 的變數了,其實這個 Symbol 就是 interface{} 所以不管要做什麼事,你都要先用 symbol.(type) 取得他真實的值和型態。接下來就可以隨意的使用他了。

範例

以下摘自 https://github.com/simbafs/go-plugin-test

main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

import (
	"bufio"
	"fmt"
	"plugin"
	"strings"
	"os"
)

func main() {
	stdin := bufio.NewReader(os.Stdin)
	for {
		fmt.Printf("> ")
		rawCmd, err := stdin.ReadString('\n')
		if err != nil {
			fmt.Println(err)
			continue
		}

		cmd := strings.Split(rawCmd, " ")
		for k, v := range cmd {
			cmd[k] = strings.TrimSpace(v)
		}

		if cmd[0] == "exit" {
			break
		}
		p, err := plugin.Open(fmt.Sprintf("./dist/%s.so", cmd[0]))
		if err != nil {
			fmt.Println(err)
			continue
		}

		f, err := p.Lookup("Exec")
		if err != nil {
			fmt.Println(err)
			continue
		}

		f.(func([]string) error)(cmd)
	}

}

plugins/ls.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "os/exec"
import "os"
import "fmt"

func Exec(args []string) error {
	ls := exec.Command("ls", args[1:]...)
	ls.Stdout = os.Stdout
	ls.Stderr = os.Stderr
	return ls.Run()
}
好想養貓阿~~
使用 Hugo 建立
主題 StackJimmy 設計