背景介绍
Golang的函数名不能使用&
取指针,因而无法直接使用unsafe.Pointer
,从而无法对函数做强制类型转换,或使用任意签名的函数作为参数。
问题分析
但是我们知道,fmt.Print
系列的函数是可以以任意函数作为入参,打印其指针的。因此我们从分析其源码作为突破口的话,或许就可以找到解决之道了。
在分析源码之后,我们发现,因为Golang的任意类型变量都可以认为是实现了interface{}
接口,因此通过将函数名转为接口,我们就可以得到一个能够取地址的变量。
这个变量不可能仅仅是一个函数指针,因为reflect
包可以通过这个变量得到其类型,即传入的函数类型。因此,该变量适用如下结构体:
// 没有方法的interface
type eface struct {
_type uintptr
data unsafe.Pointer
}
这里的data
字段即是我们想要的函数指针了。拿到这个unsafe.Pointer
之后,就可以通过强制类型转换将其复制给一个函数变量。这个函数变量的类型不一定要与传入的类型相同,因而可以实现函数的强制类型转换。
示例代码
这里使用了plugin
包生成一个plugin.so
,主函数加载该插件并将自己的Called
函数指针传递给该插件,插件将函数指针保存在自己的函数变量mainfun
,然后主函数调用插件的Call
方法,由该方法调用函数指针mainfun
,实现插件调用主函数中的函数。
主函数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.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()
}
编译运行如下:
go build -ldflags "-s -w" -o main
cd plugin
go build -buildmode=plugin -ldflags "-s -w"
cd ..
./main
输出结果如下
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!