Golang 插件类型断言

Golang plugin type assertion

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

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" 函数并尝试将其转换为函数 returning 接口然后调用它:

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

如果我 return 来自 GetProcessor 的实际实例(不是指针)并尝试将函数转换为 returning Processor,我得到同样的结果:

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

如何从插件中获取结构实例(因此加载函数 returning 它)并类型断言它是我的预期接口?

更新: 如果我从 Processor 界面中删除所有内容(也就是说,它变成一个空界面):

type Processor interface {}

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

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

我仍然得到同样的错误:

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

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

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

func GetProcessor() *Processor

您将此符号查找为 interface{} 并尝试 type-assert 类型为

的值
func() interface{}

这些类型不匹配,因为这些函数类型具有不同的 return 类型。 Spec: Function types:

A function type denotes the set of all functions with the same parameter and result types.

所以你可能只能键入assert相同的函数类型,但问题是你不能引用插件中声明的标识符(函数的return类型是插件中定义的自定义类型) .

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

另一个解决方案是将你的函数声明为return一个interface{}值,这样你就可以键入断言这个函数,你可以调用它,你将获得一个[=12]类型的值=].然后您的主应用程序可能会定义一个接口类型,其中包含您感兴趣的方法,并且在主应用程序中您可以为该接口类型键入断言。

在此处查看详细信息和示例:go 1.8 plugin use custom interface

另见相关问题:

长话短说: 在此处查看完整的工作演示: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 类型的变量,但类型断言失败,因为类型断言要求:is这个东西居然是一个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.

好的,在所有这些背景都没有的情况下,问题是您从插件中查找的符号必须完全与您的类型断言所说的类型相同, 不仅仅是 close-enough-to-allow-assignment.

如@icza 所述,您有两种选择:

选项 1:快速而简单,完成工作 在你的插件中

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

在您的主要内容中:(error-handling 为清楚起见跳过)

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