How do I call a function from the main application from a plugin?

Viewed 104

I have recently looked into Go plugins instead of manually loading .so files myself.

Basically, I have a game server application, and I want to be able to load plugins (using plugins package) when the server starts. Then, in the plugin itself, I want to be able to call exported functions that are a part of the server.

Say I have this plugin, which is compiled to example_plugin.so using go build -buildmode=plugin:

package main

import "fmt"

func init() {
    fmt.Println("Hello from plugin!")
}

Then say I have this server application, which loads the plugin (and ultimately calls the "init" function under the hood):

package main

import (
    "fmt"
    "plugin"
)

func main() {
    fmt.Println("Server started")

    if _, err := plugin.Open("example_plugin.so"); err != nil {
        panic(err)
    }
}

// some API function that loaded plugins can call
func GetPlayers() {}

The output is:

Server started
Hello from plugin!

This works as expected, however I want to be able to call that GetPlayers function (and any other exported functions in the server application, ideally) from the plugin (and any other plugins.) I was thinking about making some sort of library consisting of interfaces containing API functions that the server implements, however I have no idea where to start. I know I will probably need to use a .a file or something similar.

For clarification, I am developing this application for use on Linux, so I am fine with a solution that only works on Linux.

Apologies if this is poorly worded, first time posting on SO.

1 Answers

As mentioned in the comments, there is a Lookup function. In the documentation for the module they have the following example:

// A Symbol is a pointer to a variable or function.
// For example, a plugin defined as
//
// var V int
//
// func F() { fmt.Printf("Hello, number %d\n", V) }
//
// may be loaded with the Open function and then the exported package
// symbols V and F can be accessed

package main

import (
    "fmt"
    "plugin"
)

func main() {
    p, err := plugin.Open("plugin_name.so")
    if err != nil {
        panic(err)
    }
    v, err := p.Lookup("V")
    if err != nil {
        panic(err)
    }
    f, err := p.Lookup("F")
    if err != nil {
        panic(err)
    }
    *v.(*int) = 7
    f.(func())() // prints "Hello, number 7"
}

I think the most confusing lines here are

*v.(*int) = 7
f.(func())() // prints "Hello, number 7"

The first one of them performs a type assertion to *int to assert that v is indeed a pointer to int. That is needed since Lookup returns an interface{} and in order to do anything useful with a value, you should clarify its type.

The second line performs another type assertion, this time making sure that f is a function with no arguments and no return values, after which, immediately calls it. Since function F from the original module was referencing V (which we've replaced with 7), this call will display Hello, number 7.

Related