Golang插件类型断言

时间:2019-06-20 21:12:58

标签: go plugins type-assertion go-interface

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

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

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

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)

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

2 个答案:

答案 0 :(得分:1)

插件内的函数具有签名:

func GetProcessor() *Processor

您将此符号查找为interface{},然后尝试键入断言类型的值

func() interface{}

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

  

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

因此您只能键入断言相同的函数类型,但是问题是您不能引用插件中声明的标识符(函数的返回类型是插件中定义的自定义类型)。

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

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

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

另请参阅相关问题:

Is it possible to share a custom data type between a go plugin and an application?

Plugin symbol as function return

答案 1 :(得分:0)

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 

类似地,使用函数,这是合法的:

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