Golang插件类型断言

3

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

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: %v\n", 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

如何从插件中获取结构体实例(因此加载返回它的函数)并断言它是我期望的接口?
更新:如果我从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)

为什么指向空接口的指针无法转换?
2个回答

1

TL;DR:点击此处查看完整的演示: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 

Similarly, with functions, this is legal:

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

那行代码:procGetter, ok := procGetterInter.(*ProcessorConstructor) 给出了相同的错误:为插件simple_shout转换处理器获取器时出错:func() *main.Processor - pomo_mondreganto
@pomo_mondreganto - 是的,我的第一个答案有点偏差,我是凭记忆回答的。请看新的和改进的答案,应该可以帮助你。 - JVMATL

1
插件内部的函数具有以下签名:
func GetProcessor() *Processor

你将此符号视为 interface{} 并尝试对类型为的值进行类型断言。
func() interface{}

这些类型不匹配,因为这些函数类型具有不同的返回类型。规范:函数类型: 函数类型表示所有具有相同参数和结果类型的函数集合。
因此,您只能断言相同的函数类型,但问题在于您无法引用插件中声明的标识符(函数的返回类型是插件中定义的自定义类型)。
因此,一个简单的解决方案是将类型声明移动到另一个包中,一个公共包,该包将被插件和主应用程序(加载插件的应用程序)使用。
另一种解决方案是声明函数返回一个interface{}值,以便您可以对此函数进行类型断言,并且您可以调用它,您将获得一个interface{}类型的值。然后,您的主应用程序可以定义一个接口类型,其中包含您感兴趣的方法,在主应用程序中,您可以对此接口类型进行类型断言。
详细信息和示例请参见:go 1.8 plugin use custom interface

还可以参考相关问题:

如何在Go插件和应用程序之间共享自定义数据类型?

将插件符号作为函数返回值


问题是,我需要有许多类似的插件,它们具有不同的 Processor 接口实现。据我所知,如果我使用所描述的方法来处理我的问题,我需要创建并在主包中导入所有相应的处理器(因为 Golang 不支持函数重载),而主包甚至不需要知道插件的数量。 - pomo_mondreganto
@pomo_mondreganto,我不确定我理解你的意思,但是如果你选择后者(GetProcessor()返回interface{},并且在主应用程序中有一个单一接口,并将GetProcessor()的返回值类型断言为主应用程序中的接口),则无需导入任何内容,也不需要插件。但是当然,你的主应用程序必须加载所有插件。 - icza
@pomo_mondreganto 请注意:您需要修改插件中的 GetProcessor() 函数!将其签名更改为返回空接口:fun GetProcessor() interface{}。实现可以返回 &Processor{},这不是问题。如果您这样做,则在主应用程序中对 procGetterInter.(func() interface{}) 进行类型断言将成功。 - icza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接