【问题标题】:Plugin symbol as function return插件符号作为函数返回
【发布时间】:2018-04-04 16:44:52
【问题描述】:

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

接口定义:

package iface

type IPlugin interface{
   SayHello(string)
   SayGoodby(string)
   WhatsYourName() string
}

主程序如下所示:

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")
}

插件(存储在 pg/test.go 中)

package main

import (
    "fmt"
    "plugin_test/iface"
)

type testpl struct {}

func(pl testpl) SayHello(s string){
    fmt.Printf("Plugin says hello to %s \n", s)
}
func(pl testpl) SayGoodby(s string){
    fmt.Printf("Plugin says goodby to %s \n", s)
}
func(pl testpl) WhatsYourName() string{
    return "my name is Test-Plugin"
}

/* This function works */
func getPlugin() testpl{
    return testpl{}
}

/* This function doesn't work */
func getPlugin() iface.IPlugin{
    return testpl{}
}

/* This function also doesn't work */
func getPlugin() interface{}{
    return testpl{}
}

var Greeter = getPlugin()

我自己尝试了每个getPlugin 函数。

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

You're now connected to: my name is Test-Plugin
Plugin says hello to user
Plugin says goodby to user 

其他函数以sym.(iface.IPlugin)结束

panic: error binding plugin to interface

goroutine 1 [running]:
main.main()
        /home/../../../main.go:27 +0x343
exit status 2

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

【问题讨论】:

    标签: go plugins interface symbols


    【解决方案1】:

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

    这就是你想从插件中查找一个名为Greeter变量Plugin.Lookup() 将返回一个指向该变量的指针!如果它不会,你只能检查它的值,但你不能改变它。

    您可以通过简单地打印存储在sym 中的值的类型来验证这一点:

    fmt.Printf("%T\n", sym)
    

    在您的第一种情况func getPlugin() testpl,输出将是:

    *main.testpl
    

    在您的第二种情况func getPlugin() iface.IPlugin 中,输出将是:

    *iface.IPlugin
    

    (是的,它是一个指向接口的指针!)

    在您的第三种情况 func getPlugin() interface{} 中,输出将是:

    *interface {}
    

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

    回到你的第二个例子:func getPlugin() iface.IPlugin

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

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

    现在一切正常!

    为了避免这样的麻烦和混乱,你可以实现你的插件来公开一个函数,它会返回Greeter

    func Greeter() iface.IPlugin { return testpl{} }
    

    当然,然后摆脱 Greeter 全局变量。如果这样做,您可以查找Greeter 符号,其类型为:

    func() iface.IPlugin
    

    不同之处在于,查找函数不需要plugin 包返回指向值的指针,而如果是变量则需要。一个简单的函数类型,没有指针到接口的功夫。使用它来获取问候语将是:

    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")
    

    查看相关/类似问题:go 1.8 plugin use custom interface

    【讨论】:

    • 感谢您的详细解释。通过公共函数返回的解决方案似乎比全局变量更漂亮。我以后会用这个方法。
    猜你喜欢
    • 2014-04-23
    • 1970-01-01
    • 2021-07-10
    • 1970-01-01
    • 2011-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多