在 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)
|