Plugin symbol as function return

2020-02-14 08:32发布

问题:

I'm running into a Go behaviour which I don't understand. My idea is to import a plugin which implements an interface that is out of both packages. If a struct is returned it works fine, but to be sure it implements the interface, I want to return an interface which fails.

Interface definition:

package iface

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

The main program looks like this:

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

The plugin (stored in 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()

I tried every getPlugin function on its own.

The function returning testpl prints the expected output:

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

The other functions end on sym.(iface.IPlugin)

panic: error binding plugin to interface

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

Can someone explain why this isn't possible? Wouldn't it be easier to create a plugin if it did't let you build your plugin in such a case?

回答1:

What you want is possible, but there is something in the background that prevents it from working.

This is namely that you want to lookup a variable named Greeter from the plugin. Plugin.Lookup() will return a pointer to this variable! If it wouldn't, you could only inspect its value, but you couldn't change it.

You can verify this by simply printing the type of the value stored in sym:

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

In your first case func getPlugin() testpl, output will be:

*main.testpl

In your second case func getPlugin() iface.IPlugin, output will be:

*iface.IPlugin

(Yes, it's a pointer to an interface!)

In your third case func getPlugin() interface{}, output will be:

*interface {}

So your first example works because the value stored in sym is of type *main.testpl, which also implements iface.IPlugin (because main.testpl implements it, so does the pointer type).

Back to your 2nd example: func getPlugin() iface.IPlugin

The value stored in sym is of type *iface.IPlugin. A value of pointer type to interface never satisfies any interfaces (except the empty interface), so attempting to type-assert iface.IPlugin from a value of type *iface.IPlugin will never succeed. You have to type assert *iface.IPlugin type, which you can dereference after to obtain a value of type iface.IPlugin. It could look like this:

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

And now everything works as expected!

To avoid such hassle and confusion, you may implement your plugin to expose a function which returns you the Greeter:

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

And then get rid of the Greeter global var of course. If you do this, you may lookup the Greeter symbol which will be of type:

func() iface.IPlugin

The difference is that looking up a function does not require the plugin package to return a pointer to the value, while in case of a variable it does. A simple function type, no pointer-to-interface-kung-fu. Using it to obtain the greeter would be:

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

See related / similar question: go 1.8 plugin use custom interface