Утверждение типа плагина Golang - PullRequest
2 голосов
/ 21 июня 2019

Я пишу простое приложение, которое загружает плагин в предопределенном формате.Пример плагина следующий:

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

Как получить экземпляр структуры из плагина (поэтому загрузите функцию, возвращающую его) и тип-assert это ожидаемый интерфейс в моем случае?

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 ]

1 голос
/ 21 июня 2019

Функция внутри плагина имеет подпись:

func GetProcessor() *Processor

Вы ищите этот символ как interface{}, и вы пытаетесь ввести значение типа

func() interface{}

Эти типы не совпадают, потому что эти типы функций имеют разные возвращаемые типы. Спецификация: Типы функций:

Тип функции обозначает набор всех функций с одинаковыми параметрами и типами результатов .

Таким образом, вы можете печатать assert только один и тот же тип функции, но проблема в том, что вы не можете ссылаться на идентификаторы, объявленные в плагине (возвращаемый тип функции - это пользовательский тип, определенный в плагине).

Таким образом, простое решение состоит в том, чтобы переместить объявление типа в другой пакет, общий пакет, который будет использоваться как плагином, так и основным приложением (которое загружает плагин).

Другое решение - объявить вашу функциювернуть значение interface{}, чтобы вы могли ввести assert и вызвать ее, и вы получите значение типа interface{}.Тогда ваше основное приложение может определить тип интерфейса, содержащий интересующие вас методы, и в основном приложении вы можете ввести assert для этого типа интерфейса.

Подробности и примеры приведены здесь: go 1.8 pluginиспользовать пользовательский интерфейс

Также см. связанные вопросы:

Можно ли совместно использовать пользовательский тип данных между плагином go и приложением?

Плагин-символ как возврат функции

0 голосов
/ 21 июня 2019

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"))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...