Go 插件符号作为函数返回

Go 插件符号作为函数返回,go,plugins,interface,symbols,Go,Plugins,Interface,Symbols,我遇到了一种我不理解的行为。我的想法是导入一个插件,它实现了两个包之外的接口。如果返回一个结构,它工作正常,但是为了确保它实现了接口,我想返回一个失败的接口 接口定义: 包iface 类型IPlugin接口{ 你好(字符串) SayGoodby(字符串) WhatsYourName()字符串 } 主程序如下所示: package main import ( "plugin" "plugin_test/iface" "errors" "fmt" ) //go:

我遇到了一种我不理解的行为。我的想法是导入一个插件,它实现了两个包之外的接口。如果返回一个结构,它工作正常,但是为了确保它实现了接口,我想返回一个失败的接口

接口定义:

包iface
类型IPlugin接口{
你好(字符串)
SayGoodby(字符串)
WhatsYourName()字符串
}
主程序如下所示:

package main

import (
    "plugin"
    "plugin_test/iface"
    "errors"
    "fmt"
)

//go:generate go build -buildmode=plugin -o ./pg/test.so ./pg/test.go

func main(){
    path := "pg/test.so"
    plug, err := plugin.Open(path)
    if err != nil {
        panic(err)
    }

    sym, err := plug.Lookup("Greeter")
    if err != nil {
        panic(err)
    }

    var pg iface.IPlugin
    pg, ok := sym.(iface.IPlugin)
    if !ok {
        panic(errors.New("error binding plugin to interface"))
    }

    fmt.Printf("You're now connected to: %s \n", pg.WhatsYourName())
    pg.SayHello("user")
    pg.SayGoodby("user")
}
pgPtr, ok := sym.(*iface.IPlugin)
if !ok {
    panic(errors.New("error binding plugin to interface"))
}
pg := *pgPtr
插件(存储在pg/test.go中)

我自己尝试了每个
getPlugin
函数

返回的函数
testpl
打印预期输出:

您现在已连接到:我的名字是Test Plugin
插件向用户问好
插件对用户说再见
其他函数结束于
sym.(iface.IPlugin)

panic:将插件绑定到接口时出错
goroutine 1[正在运行]:
main.main()
/主页/../../../main.go:27+0x343
退出状态2

有人能解释为什么这是不可能的吗?如果在这种情况下不允许您构建插件,那么创建插件不是更容易吗?

您想要的是可能的,但是后台有一些东西阻止它工作

也就是说,您希望从插件中查找名为
Greeter
的变量。将返回指向此变量的指针!如果它不能,您只能检查它的值,但无法更改它

您只需打印存储在
sym
中的值类型即可验证这一点:

fmt.Printf("%T\n", sym)
在第一种情况下,
func getPlugin()testpl
,输出将是:

*main.testpl
*iface.IPlugin
*interface {}
在第二种情况下,
func getPlugin()iface.IPlugin
,输出将是:

*main.testpl
*iface.IPlugin
*interface {}
(是的,它是指向接口的指针!)

在第三种情况下,
func getPlugin()接口{}
,输出将是:

*main.testpl
*iface.IPlugin
*interface {}
因此,您的第一个示例之所以有效,是因为
sym
中存储的值属于
*main.testpl
类型,它也实现了
iface.IPlugin
(因为
main.testpl
实现了它,指针类型也实现了它)

回到第二个示例:
func getPlugin()iface.IPlugin

存储在
sym
中的值的类型为
*iface.IPlugin
。指向接口的指针类型的值永远不会满足任何接口(空接口除外),因此尝试从类型为
*iface.IPlugin
的值中键入assert
iface.IPlugin
将永远不会成功。您必须键入assert
*iface.IPlugin
type,然后可以取消引用以获得类型为
iface.IPlugin
的值。它可能是这样的:

package main

import (
    "plugin"
    "plugin_test/iface"
    "errors"
    "fmt"
)

//go:generate go build -buildmode=plugin -o ./pg/test.so ./pg/test.go

func main(){
    path := "pg/test.so"
    plug, err := plugin.Open(path)
    if err != nil {
        panic(err)
    }

    sym, err := plug.Lookup("Greeter")
    if err != nil {
        panic(err)
    }

    var pg iface.IPlugin
    pg, ok := sym.(iface.IPlugin)
    if !ok {
        panic(errors.New("error binding plugin to interface"))
    }

    fmt.Printf("You're now connected to: %s \n", pg.WhatsYourName())
    pg.SayHello("user")
    pg.SayGoodby("user")
}
pgPtr, ok := sym.(*iface.IPlugin)
if !ok {
    panic(errors.New("error binding plugin to interface"))
}
pg := *pgPtr
现在一切都按预期进行

为了避免这种麻烦和混乱,您可以实现插件以公开一个函数,该函数将返回您的
问候者

func Greeter() iface.IPlugin { return testpl{} }
当然,然后去掉
迎宾员
全局变量。如果执行此操作,您可以查找以下类型的
迎宾员
符号:

func() iface.IPlugin
不同之处在于,查找函数不需要
插件
包返回指向该值的指针,而对于变量则需要。一个简单的函数类型,没有指向接口的指针。使用它来获得迎宾员将是:

Greeter, err := p.Lookup("Greeter")
if err != nil {
    panic(err)
}
greeterFunc, ok := GetFilter.(func() iface.IPlugin)
if !ok {
    panic(errors.New("not of expected type"))
}
greeter := greeterFunc()

// And using it:
fmt.Printf("You're now connected to: %s \n", greeter.WhatsYourName())
greeter.SayHello("user")
greeter.SayGoodby("user")

请参阅相关/类似问题:

谢谢您的详细解释。通过公共函数返回的解决方案似乎比全局变量更漂亮。我将在将来使用此方法。