background introduction
Golang's function name cannot use &
pointers, so it cannot be used directly unsafe.Pointer
, so it is impossible to cast the function or use a function with any signature as a parameter.
problem analysis
But we know that fmt.Print
a series of functions can take any function as an input parameter and print its pointer. Therefore, if we analyze its source code as a breakthrough, we may be able to find a solution.
After analyzing the source code, we found that because any type of variable in Golang can be considered as implementing interface{}
an interface, by converting the function name into an interface, we can get a variable that can take an address.
This variable cannot be just a function pointer, because reflect
the package can get its type through this variable, that is, the type of the function passed in. Therefore, the variable applies to the following structure:
// 没有方法的interface
type eface struct {
_type uintptr
data unsafe.Pointer
}
The field here data
is the function pointer we want. Once you have this unsafe.Pointer
, you can copy it to a function variable via a cast. The type of this function variable does not have to be the same as the type passed in, so the mandatory type conversion of the function can be realized.
sample code
Here a package is used to plugin
generate one plugin.so
, the main function loads the plug-in and passes its own Called
function pointer to the plug-in, the plug-in saves the function pointer in its own function variable mainfun
, and then the main function calls Call
the method of the plug-in, which calls the function pointer mainfun
to achieve Plugins call functions in the main function.
main function main.go
package main
import (
"fmt"
"plugin"
)
func Called() {
fmt.Println("main func is called!")
}
func main() {
h, err := plugin.Open("plugin/plugin.so")
var initfun, callfun plugin.Symbol
if err == nil {
initfun, err = h.Lookup("Inita")
if err == nil {
callfun, err = h.Lookup("Call")
if err == nil {
fmt.Println("addr from main:", Called)
initfun.(func(interface{
}))(Called)
Called()
callfun.(func())()
}
}
}
if err != nil {
fmt.Println(err)
}
}
plugin-plugin/plugin.go
package main
import (
"fmt"
"reflect"
"unsafe"
)
var mainfun func()
// 没有方法的interface
type eface struct {
_type uintptr
data unsafe.Pointer
}
func Inita(ptr interface{
}) {
fmt.Println("addr val of ptr:", ptr)
fmt.Printf("addr val of unsafe: 0x%x\n", reflect.ValueOf(ptr).Pointer())
f := (*eface)(unsafe.Pointer(&ptr)).data
mainfun = *(*(func()))(unsafe.Pointer(&f))
fmt.Println("addr from plug:", mainfun)
}
func Call() {
mainfun()
}
Compile and run as follows:
go build -ldflags "-s -w" -o main
cd plugin
go build -buildmode=plugin -ldflags "-s -w"
cd ..
./main
The output is as follows
addr from main: 0x40cdc40
addr val of ptr: 0x40cdc40
addr val of unsafe: 0x40cdc40
addr from plug: 0x40cdc40
main func is called!
main func is called!