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

【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

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Golang使用标签表达式校验结构体字段的有效性

    在服务的API接口层面,我们常常需要验证参数的有效性。 Golang中,大部分参数校验场景实际上是先将数据Bind到结构体,然后校验其字段值。

    henrylee2cn
  • GB2312、GBK、GB18030 这几种字符集的主要区别是什么?

    1 GB2312-80 GB 2312 或 GB 2312-80 是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,又称 GB 0,...

    henrylee2cn
  • [Go小技巧] 教你如何将前端文件打包进Go程序,Cool!

    在Golang的开发中,我们有时会想要将一些外部依赖文件打包进二进制程序。比如本人在开发lessgo web框架时,希望将扩展包swagger(一个自动API文...

    henrylee2cn
  • 二十款免费WiFi黑客(渗透测试)工具

    对于企业的IT经理和网络管理员来说,保护相对脆弱的WiFi网络的最佳办法就拿黑客圈最流行的,“火力”最猛的工具入侵你自己的无线网络,从中找出网络安全漏洞并有针对...

    洛米唯熊
  • 「无中生有」计算机视觉探奇

    计算机视觉 (Computer Vision, CV) 是一门研究如何使机器“看”的科学。1963年来自MIT的Larry Roberts发表的该领域第一篇博士...

    AI研习社
  • 详解motif的PFM矩阵

    在之前的文章中,对motif的几个基本概念进行了简单介绍。一致性序列采用IUPAC碱基表示标准来描述motif的序列信息,sequence logo是结合碱基分...

    生信修炼手册
  • 单元测试 & mocha 简述

    这个最小测试单元,可以是一个函数,可以是一个类,可以是一个对象,也可以是一个组件,一个插件

    IMWeb前端团队
  • HAWQ取代传统数仓实践(二)——搭建示例模型(MySQL、HAWQ)

    一、业务场景         本系列实验将应用HAWQ数据库,为一个销售订单系统建立数据仓库。本篇说明示例的业务场景、数据仓库架构、实验环境、源和目标库的建立过...

    用户1148526
  • 简单http和https服务器pytho

    工作经常要用到测试http和https协议,这里写了两个简单的脚本实现简单的http服务器和https服务器。

    py3study
  • Limber教你如何进行调参

    專 欄 ❈Limber,Python中文社区专栏作者,Python中文社区新Logo设计人,纯种非CS科班数据分析人,沉迷Keras。在Cambridge做了...

    Python中文社区

扫码关注云+社区

领取腾讯云代金券