【Go unsafe进阶】执行空接口中的函数:除了断言与反射,你还有更好的选择

假如我们有这样一个包: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。

  • 计算word偏移量,在64位系统中,该值为8:
ptrOffset := unsafe.Offsetof(new(emptyInterface).word)
  • 获取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

转载于:https://my.oschina.net/henrylee2cn/blog/3030797

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值