Символ плагина как возврат функции

Я сталкиваюсь с поведением Go, которого не понимаю. Моя идея состоит в том, чтобы импортировать плагин, который реализует интерфейс, не входящий в оба пакета. Если возвращается структура, она работает нормально, но чтобы убедиться, что она реализует интерфейс, я хочу вернуть интерфейс, который не работает.

Определение интерфейса:

package iface

type IPlugin interface{
   SayHello(string)
   SayGoodby(string)
   WhatsYourName() string
}

Основная программа выглядит так:

package main

import (
    "plugin"
    "plugin_test/iface"
    "errors"
    "fmt"
)

//go:generate go build -buildmode=plugin -o ./pg/test.so ./pg/test.go

func main(){
    path := "pg/test.so"
    plug, err := plugin.Open(path)
    if err != nil {
        panic(err)
    }

    sym, err := plug.Lookup("Greeter")
    if err != nil {
        panic(err)
    }

    var pg iface.IPlugin
    pg, ok := sym.(iface.IPlugin)
    if !ok {
        panic(errors.New("error binding plugin to interface"))
    }

    fmt.Printf("You're now connected to: %s \n", pg.WhatsYourName())
    pg.SayHello("user")
    pg.SayGoodby("user")
}

Плагин (хранится в pg / test.go)

package main

import (
    "fmt"
    "plugin_test/iface"
)

type testpl struct {}

func(pl testpl) SayHello(s string){
    fmt.Printf("Plugin says hello to %s \n", s)
}
func(pl testpl) SayGoodby(s string){
    fmt.Printf("Plugin says goodby to %s \n", s)
}
func(pl testpl) WhatsYourName() string{
    return "my name is Test-Plugin"
}

/* This function works */
func getPlugin() testpl{
    return testpl{}
}

/* This function doesn't work */
func getPlugin() iface.IPlugin{
    return testpl{}
}

/* This function also doesn't work */
func getPlugin() interface{}{
    return testpl{}
}

var Greeter = getPlugin()

Я пробовал каждую функцию getPlugin по отдельности.

Функция, возвращающая testpl, печатает ожидаемый результат:

You're now connected to: my name is Test-Plugin
Plugin says hello to user
Plugin says goodby to user 

Остальные функции заканчиваются на sym.(iface.IPlugin).

panic: error binding plugin to interface

goroutine 1 [running]:
main.main()
        /home/../../../main.go:27 +0x343
exit status 2

Может кто-нибудь объяснить, почему это невозможно? Разве не было бы проще создать плагин, если бы он не позволял вам создавать свой плагин в таком случае?

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
3
0
524
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

То, что вы хотите, возможно, но что-то в фоновом режиме мешает этому.

Это именно то, что вы хотите найти в плагине Переменная с именем Greeter. Plugin.Lookup() вернет указатель на эту переменную! В противном случае вы могли бы только проверить его значение, но вы не могли его изменить.

Вы можете проверить это, просто распечатав тип значения, хранящегося в sym:

fmt.Printf("%T\n", sym)

В вашем первом случае func getPlugin() testpl вывод будет:

*main.testpl

Во втором случае func getPlugin() iface.IPlugin вывод будет:

*iface.IPlugin

(Да, это указатель на интерфейс!)

В третьем случае func getPlugin() interface{} вывод будет:

*interface {}

Итак, ваш первый пример работает, потому что значение, хранящееся в sym, относится к типу *main.testpl, который также реализует iface.IPlugin (поскольку main.testpl реализует его, также как и тип указателя).

Вернемся к вашему 2-му примеру: func getPlugin() iface.IPlugin

Значение, хранящееся в sym, относится к типу *iface.IPlugin. Значение типа указателя на интерфейс никогда не удовлетворяет никаким интерфейсам (кроме пустого интерфейса), поэтому попытка утверждения типа iface.IPlugin из значения типа *iface.IPlugin никогда не увенчается успехом. Вам необходимо ввести assert типа *iface.IPlugin, после чего вы можете разыменовать его, чтобы получить значение типа iface.IPlugin. Это могло выглядеть так:

pgPtr, ok := sym.(*iface.IPlugin)
if !ok {
    panic(errors.New("error binding plugin to interface"))
}
pg := *pgPtr

И теперь все работает как положено!

Чтобы избежать таких хлопот и путаницы, вы можете реализовать свой плагин, чтобы предоставить функция, который возвращает вам Greeter:

func Greeter() iface.IPlugin { return testpl{} }

И затем, конечно же, избавьтесь от глобальной переменной Greeter. Если вы сделаете это, вы можете найти символ Greeter, который будет иметь тип:

func() iface.IPlugin

Разница в том, что для поиска функции не требуется, чтобы пакет plugin возвращал указатель на значение, тогда как в случае переменной это так. Простой тип функции, без указателя на интерфейс-кунг-фу. Использование его для получения приветствующего будет:

Greeter, err := p.Lookup("Greeter")
if err != nil {
    panic(err)
}
greeterFunc, ok := GetFilter.(func() iface.IPlugin)
if !ok {
    panic(errors.New("not of expected type"))
}
greeter := greeterFunc()

// And using it:
fmt.Printf("You're now connected to: %s \n", greeter.WhatsYourName())
greeter.SayHello("user")
greeter.SayGoodby("user")

См. Связанный / похожий вопрос: плагин go 1.8 использует настраиваемый интерфейс

Спасибо за подробное объяснение. Решение о возврате с помощью публичной функции кажется намного красивее, чем глобальная переменная. Я буду использовать этот метод в будущем.

Roman Odermatt 06.04.2018 15:31

Другие вопросы по теме