目录

突破限制,在 Go 中调用非导出方法

调用非导出函数

在 go 语言中,所有的导出与非导出都由标志符的第一个字母是否大写决定。 有时候,我们可能需要调用一个私有方法或私有函数。对于函数,我们可以使用 linkname 解决这个问题。

例如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package a

func test() {
	println("hello world from myproject/a")
}

type A struct {}

func (*A) test1()  {
    println("test1")
}

func (A) test2()  {
    println("test2")
}

如果我们需要调用 a 包中的 test 函数,使用 linkname 可以很轻松的做到。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
	_ "unsafe" // linkname require unsafe
	
	"myproject/a"
)

func main() {
	test()
}

//go:linkname test myproject/a.test
func test()

调用非导出方法

但是如果我们需要调用类型A的两个私有方法, linkname似乎就不太好用了。

反射?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
	"reflect"
	
	"myproject/a"
)

func main() {
	var v a.A
	rv := reflect.ValueOf(v)
	rv.MethodByName("test1").Call([]reflect.Value{})
}

这似乎是一条可行的路, 但是 reflect 包会强制检查函数的导出性, 调用非导出函数是被禁止的。

将方法回归函数

对于 go 来说,所有的方法都会被编译成一个包含接收器的函数。同时 go 也提供了另外一种方法调用方法。

例如:

1
2
3
4
5
6
file, err := os.Open("some file")
if err != nil {
	panic(err)
}
buf := make([]byte, 1024)
file.Read(buf)

可以等价的编写为

1
2
3
4
5
6
file, err := os.Open("some file")
if err != nil {
	panic(err)
}
buf := make([]byte, 1024)
(*os.File).Read(file, buf)

所以我们调用非导出函数的思路也复现出来了:

go 编译器会将方法

1
func (receiver *Type) SomeFunc(args...)

编译成函数

1
func SomeFunc(receiver *Type,args ...)

利用这个特性,我们可以将方法转换成函数,这样我们就可以使用调用非导出函数的方法,处理非导出方法了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
	_ "unsafe" // linkname require unsafe
	
	"myproject/a"
)

func main() {
	var v a.A
	test1(&a)
	test2(a)
}

// 指针接收器
//go:linkname test myproject/a.A.test1
func test1(_ *a.A)

// 非指针接收器
//go:linkname test myproject/a.(*A).test2
func test2(_ a.A)