2

I'm writing a simple app which loads plugin in a predefined format. Example plugin is the following:

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{}
}

I can't quite apprehend how to load such a structure in my main program. So, I declare an interface:

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

Then I load "getter" function and try to cast it to a function returning interface to then call it:

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

But the cast fails with an error:

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

If I return an actual instance (not a pointer) from GetProcessor and try to cast the function to the one returning Processor, I get the same result:

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

How to get a struct instance from plugin (therefore load the function returning it) and type-assert it's an expected interface in my case?

UPD: If I remove everything from Processor interface (that is, it becomes just an empty interface):

type Processor interface {}

And try to cast procGetterInter to a function returning a pointer to Processor interface:

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

I still get the same error:

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

Why doesn't it cast even to pointer to an empty interface?

4

2 回答 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 

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

    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给 type 的变量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"))
于 2019-06-21T03:35:26.483 回答
1

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

func GetProcessor() *Processor

您将此符号查找为 aninterface{}并尝试键入断言类型的值

func() interface{}

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

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

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

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

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

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

另请参阅相关问题:

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

插件符号作为函数返回

于 2019-06-20T21:38:16.030 回答