Go 1.8支持Go插件。
我创建了两个插件,如下所示。
据我了解,该插件仅公开main包中的函数和变量。即plugin.Lookup()对于非main变量/函数将失败。
main
plugin.Lookup()
但是我想测试一个插件是否可以在内部从另一个插件调用方法,类似于C ++库如何调用另一个库。
所以我测试如下:
plugin1 github.com/vimal/testplugin
$ cat myplugin.go package main import "C" import "fmt" import help "github.com/vimal/testplugin1/plug" func init() { fmt.Printf("main.init invoked\n") } // TestPlugin func TestPlugin() string { return help.Help() }
plugin2 github.com/vimal/testplugin1
$ cat myplugin.go package main import "C" func HelperFunc() string { return "help" } $ cat plug/helper.go package help func Help() string { return "help234" }
这里的想法是,plugin1调用plugin2的内部非main功能。
主程序
主程序加载许多作为参数指定的插件,并TestPlugin()从最后一个插件调用。
TestPlugin()
测试1:
构建两个插件,加载两个插件,然后调用invoke TestPlugin(),输出包含"help234",即调用内部函数。可以理解,因为两个插件都已加载,所以一个插件可以调用另一个插件的内部代码。
"help234"
测试2:
仅加载plugin1,然后调用TestPlugin(),输出包含"help234",即调用内部函数。观察到与test1相同的输出。也许这次是从中找到该方法的GOPATH。
GOPATH
测试3:
将文件夹重命名"github.com/vimal/testplugin1"为"github.com/vimal/junk1",删除plugin2,仅加载plugin1,然后调用TestPlugin()。输出仍然包含"help234",即调用内部函数。
"github.com/vimal/testplugin1"
"github.com/vimal/junk1"
我无法理解test3如何产生相同的输出。plugin1是否也包含plugin2代码?如何理解Go插件对其他Go插件的依赖性?
转到版本: go version go1.8rc3 linux/amd64
go version go1.8rc3 linux/amd64
您没有完全按照自己的想法去做。
您的 plugin1 导入并使用一个 软件包 ,即github.com/vimal/testplugin1/plug。这与 plugin2 不相等!
github.com/vimal/testplugin1/plug
这里发生的是,当您构建 plugin1时 ,其所有依赖项都被构建到插件文件中,包括.../testplugin1/plug软件包。并且当您加载 plugin1时 ,其所有依赖项也将从插件文件(包括plug软件包)中加载。此后,无论 plugin2 的加载状态如何 都 可以正常工作 也就不足为奇了 。这两个插件彼此独立。
.../testplugin1/plug
plug
该-buildmode=plugin指令指示编译器要构建插件而不是独立应用程序,但这并不意味着必须不包含依赖项。它们必须是这样,因为插件不能完全保证Go应用程序将加载它,以及Go应用程序将拥有什么软件包。因为可运行的应用程序甚至仅包含应用程序本身明确引用的标准库中的程序包。
-buildmode=plugin
保证插件具有所需一切的唯一方法,并且如果它还包含其所有依赖项(包括来自标准库的依赖项),则它将可以正常工作。(这就是为什么构建简单的插件会生成相对较大的文件的原因,类似于构建简单的Go可执行文件会生成大文件的原因。)
几乎不需要添加到插件中的东西都包括Go运行时,例如,因为将要加载插件的正在运行的Go应用程序已经在运行Go运行时。(请注意,您只能从使用相同版本的Go编译的应用程序中加载插件。)但是除此之外,该插件还必须包含所需的一切。
Go是一种静态链接的语言。编译Go应用或插件后,它们将不再依赖或检查的值GOPATH,仅在构建它们时由Go工具使用。
您的主应用程序和插件可能引用相同的程序包(导入路径“相同”)。在这种情况下,将仅使用包装的一个“实例”。
如果此通用包具有“状态”,例如全局变量,则可以对此进行测试。让我们假设一个通用的共享软件包mymath:
mymath
package mymath var S string func SetS(s string) { S = s }
还有一个pg使用它的插件:
pg
package main import ( "C" "mymath" "fmt" ) func Start() { fmt.Println("pg:mymath.S", mymath.S) mymath.SetS("pghi") fmt.Println("pg:mymath.S", mymath.S) }
以及使用mymath和加载pg(使用它)的主应用程序:
package main import ( "plugin" "mymath" "fmt" ) func main() { fmt.Println("mymath.S", mymath.S) mymath.SetS("hi") fmt.Println("mymath.S", mymath.S) p, err := plugin.Open("../pg/pg.so") if err != nil { panic(err) } start, err := p.Lookup("Start") if err != nil { panic(err) } start.(func())() fmt.Println("mymath.S", mymath.S) }
构建插件:
cd pg go build -buildmode=plugin
运行主应用程序,输出为:
mymath.S mymath.S hi pg:mymath.S hi pg:mymath.S pghi mymath.S pghi
分析:首先,主应用程序开始运行mymath.S,然后将其设置为"hi"最终。然后是插件,它会打印它(我们看到了主应用程序设置的值"hi"),然后将其更改为"pghi"。然后再次出现主应用程序并打印mymath.S,然后再次看到插件设置的最后一个值:"pghi"。
mymath.S
"hi"
"pghi"
因此,仅存在一个“实例” mymath。现在,如果继续进行更改mymath,例如将重命名myMath.SetS()为mymath.SetS2(),然后在主应用程序中将调用更新为mymath.SetS2("hi"),而无需重新构建插件,只需运行主应用程序,您将获得以下输出:
myMath.SetS()
mymath.SetS2()
mymath.SetS2("hi")
mymath.S mymath.S hi panic: plugin.Open: plugin was built with a different version of package mymath goroutine 1 [running]: main.main() <GOPATH>/src/play/play.go:16 +0x4b5 exit status 2
如您所见,在构建主应用程序和插件时,将记录软件包版本(很可能是哈希),如果它们的导入路径在主应用程序和插件中匹配,则必须匹配。
(请注意,如果您不更改使用过的mymath包的导出标识符(和签名),而仅更改实现,则也会出现上述错误;例如func SetS(s string) { S = s + "+" }。)
func SetS(s string) { S = s + "+" }