假如我们有这样一个包:iface.go
package iface
func GetAddFunc() interface{} {
return add
}
type i32 int32
func add(a, b i32) i32 {
return a + b
}
希望可以在包外执行add函数,怎么办?此处,因为该函数签名是不可导出的,所以,正常思路是使用反射,代码可能是这样:
import (
"fmt"
"iface"
"reflect"
)
func main() {
addIface := iface.GetAddFunc()
ret := reflect.ValueOf(addIface).Call([]reflect.Value{
reflect.ValueOf(1),
reflect.ValueOf(2),
})[0].Int()
fmt.Println(ret) // expect: 3
}
但是,运行时发现 panic 了?!错误信息如下:
panic: reflect: Call using int as type iface.i32
这可咋办?使用断言?类型不可导出,更加不可能! 这时,unsafe的高阶用法就派上用场了。先上最终代码,再来分析实现思路:
import (
"fmt"
"iface"
"unsafe"
"github.com/henrylee2cn/goutil/tpack"
)
func main() {
addIface := iface.GetAddFunc()
ptr := tpack.Unpack(addIface).Pointer()
add := *(*func(a, b int32) int32)(unsafe.Pointer(&ptr))
fmt.Println(add(1, 2)) // Output: 3
}
该代码,使用了本人开源的unsafe进阶包tpack。 它可以帮助我们拿到函数的Pointer,之后我们就能通过unsafe强转成外部定义的等价类型func(a, b int32) int32
,进而执行它。
这种方法不但适用范围广泛,而且执行时没有任何性能损耗(等价于直接调用iface.add
)
那么,让我们了解一下tpack.Unpack(iface.GetAddFunc()).Pointer()
这行代码做了什么事情呢?
首先,addIface
是接口类型,而接口类型的底层类型原型如下:
emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
typ字段存储类型信息,word字段存储指向数据的位置信息。我们当前需求只关心word。通过偏移量,我们可以拿到word的值,进而拿到函数在内存中的起始位置,即Pointer。
ptrOffset := unsafe.Offsetof(new(emptyInterface).word)
word := uintptr(unsafe.Pointer(&addIface)) + ptrOffset
ptr := *(*uintptr)(unsafe.Pointer(word))
ptr
表示函数的起始位置,而unsafe.Pointer要求是变量的指针,因此,需要使用 &ptr
进行指针类型的强转:add := *(*func(a, b int32) int32)(unsafe.Pointer(&ptr))
更多有趣的unsafe进阶操作,可以了解 tpack
我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2pkhijryroysk
(adsbygoogle = window.adsbygoogle || []).push({});