Golang 插件类型断言



我正在编写一个简单的应用程序,它以预定义的格式加载插件。示例插件如下:

package main
import (
"errors"
"fmt"
"strings"
)
var (
ok        bool
InvConfig = errors.New("invalid config")
)
type Processor struct {
logEverything bool
}
func (p *Processor) Init(config map[string]interface{}) error {
p.logEverything, ok = config["log_everything"].(bool)
if !ok {
return InvConfig
}
return nil
}
func (p *Processor) Process(buf []byte) []byte {
if p.logEverything {
fmt.Printf("Shouter got data: %vn", buf)
}
return []byte(strings.ToUpper(string(buf)))
}
func GetProcessor() *Processor {
return &Processor{}
}

我不太明白如何在我的主程序中加载这样的结构。因此,我声明了一个接口:

type Processor interface {
Init(map[string]interface{}) error
Process(buf []byte) []byte
}

然后我加载"getter"函数并尝试将其转换为返回接口的函数,然后调用它:

p, err := plugin.Open(filepath)
if err != nil {
logrus.Fatalf("Error opening plugin %s: %v", pluginName, err)
}
procGetterInter, err := p.Lookup("GetProcessor")
if err != nil {
logrus.Fatalf("Error loading processor getter for plugin %s: %v", pluginName, err)
}
procGetter, ok := procGetterInter.(func() interface{})
if !ok {
logrus.Fatalf("Error casting processor getter for plugin %s: %T", pluginName, procGetterInter)
}

但强制转换失败并显示错误:

Error casting processor getter for plugin simple_shout: func() *main.Processor

如果我从GetProcessor返回一个实际的实例(不是指针),并尝试将函数转换为返回Processor的函数,我会得到相同的结果:

Error casting processor getter for plugin simple_shout: func() main.Processor

如何从插件获取结构实例(因此加载返回它的函数)并在我的情况下类型断言它是一个预期的接口?

UPD:如果我从Processor接口中删除所有内容(也就是说,它只是一个空接口):

type Processor interface {}

并尝试将procGetterInter转换为返回指向Processor接口的指针的函数:

procGetter, ok := procGetterInter.(func() *Processor)

我仍然收到相同的错误:

plugin.Symbol is func() *main.Processor, not func() *main.Processor (types from different scopes)

为什么它甚至不强制转换为指向空接口的指针?

TL;博士: 在此处查看完整的工作演示:https://github.com/jvmatl/go-plugindemo


冗长但(希望如此!)内容丰富的答案:

插件在几个方面都很棘手,@icza的答案是完全正确的,但要理解它为什么是正确的以及它如何应用于你的问题,你需要了解 go 接口的灵活性不适用于复杂类型。

您可能已经在其他上下文中遇到过此问题:

这在 Go 中是合法的:

var a interface{}
var b int
a = b // yep, an int meets the spec for interface{} !

但这不是:

var aa []interface{}
var bb []int
aa = bb // cannot use bb (type []int) as type []interface {} in assignment 

同样,对于函数,这是合法的:

type Runner interface {
Run()
}
type UsainBolt struct{}
func (ub *UsainBolt) Run() {
fmt.Println("Catch me if you can!")
}
var a Runner
var b *UsainBolt
a = b // Yep, a (pointer to) Usain Bolt is a runner!

但这不是:

var aa func() Runner
var bb func() *UsainBolt
aa = bb // cannot use bb (type func() *UsainBolt) as type func() Runner in assignment

现在让我们看一下定义的函数类型。这就是它变得非常有趣的地方:

type RunnerGetter func() Runner
var rg RunnerGetter
rg = getUsain  // <-- Nope: doesn't compile: "cannot use getUsain (type func() *UsainBolt) as type RunnerGetter in assignment"
rg = getRunner // <-- This *assignment* is allowed: getRunner is assignable to a type RunnerGetter
var i interface{} = getRunner
rg = i.(RunnerGetter) // compiles, but panics at runtime: "interface conversion: interface {} is func() main.Runner, not main.RunnerGetter"

换句话说,语言可以将func getRunner() Runner赋值给类型为RunnerGetter的变量,但类型断言失败,因为类型断言在问:这个东西实际上是 RunnerGetter 类型的变量吗?答案是否定的,这是一个接近但不太正确的func() Runner,所以我们恐慌。

但这有效:

var rg RunnerGetter
var i interface{}
i = rg // after this assignment, i *is* a RunnerGetter
rg = i.(RunnerGetter) // so this assertion passes.

好的,有了所有这些背景,问题是你从插件中查找的符号必须与你的类型断言所说的类型完全相同,而不仅仅是足够接近允许赋值。

如@icza所述,您有以下几种选择:

选项1:快速而肮脏,在您的插件中完成工作

func GetGeneric() interface{} {
return &Processor{}
}

在您的主要内容中:(为清楚起见,跳过了错误处理)

p, _ := plugin.Open(pluginFile)                  // load plugin
newIntf, _ := p.Lookup("Getgeneric")             // find symbol
newProc, _ := newIntf.(func() interface{})       // assert symbol to generic constructor
shoutProc, _ := newProc().(processors.Processor) // call generic constructor, type assert the return value
// Now use your new plugin!
shoutProc.Init(map[string]interface{}{"log_everything": true}) 
output := shoutProc.Process([]byte("whisper"))

选项 2:更干净,如果你有很多插件,那就更好了声明接口,所有插件都必须在另一个包中满足:

package processors
// Every plugin must be able to give me something that meets this interface
type Processor interface {
Init(map[string]interface{}) error
Process(buf []byte) []byte
}

在您的插件中:

type ShoutProcessor struct {
configured    bool
logEverything bool
}
func NewProcessor() processors.Processor {
return &ShoutProcessor{}
}

在您的主要:

p, _ := plugin.Open(pluginFile)             // load plugin
newProcIntf, _ := p.Lookup("NewProcessor")  // lookup constructor
newProc, _ := newProcIntf.(func() processors.Processor) // assert the type of the func
shoutProc := newProc() // call the constructor, get a new ShoutProcessor
// ready to rock and roll!
shoutProc.Init(map[string]interface{}{"log_everything": true})
output := shoutProc.Process([]byte("whisper"))

插件内部的函数有一个签名:

func GetProcessor() *Processor

将此符号查找为interface{},并尝试键入类型的值

func() interface{}

这些类型不匹配,因为这些函数类型具有不同的返回类型。规格: 函数类型:

函数

类型表示具有相同参数和结果类型的所有函数的集合。

所以你可能只类型断言相同的函数类型,但问题是你不能引用插件中声明的标识符(函数的返回类型是插件中定义的自定义类型)。

因此,一个简单的解决方案是将类型声明移动到另一个包,一个将由插件和主应用程序(加载插件)使用的通用包。

另一种解决方案是声明您的函数以返回一个interface{}值,以便您可以键入断言此函数,并且可以调用它,您将获得一个类型为interface{}的值。然后,您的主应用程序可能会定义一个包含您感兴趣的方法的接口类型,并且在主应用程序中,您可以键入 assert 到此接口类型。

在此处查看详细信息和示例:go 1.8 插件使用自定义界面

另请参阅相关问题:

是否可以在 go 插件和应用程序之间共享自定义数据类型?

插件符号作为函数返回

最新更新