在go语言的源码中,会发现很多,代码只有函数签名,却看不到函数体,如:
// src/os/proc.go 68行
func runtime_beforeExit() // implemented in runtime
此处我们只看到函数签名,却看不到函数体,全局搜了一把,发现它的函数体却定义在src/runtime/proc.go
中
// os_beforeExit is called from os.Exit(0).
//go:linkname os_beforeExit os.runtime_beforeExit
func os_beforeExit() {
if raceenabled {
racefini()
}
}
它是通过go:linkname
把函数签名和函数体连接在一起的。
指令格式:
//go:linkname B A
第一个参数表示当前方法或变量,第二个参数表示需要建立链接方法,变量的路径。
go:linkname
引导编译器将当前(私有)方法或者变量在编译时链接到指定的位置的方法或者变量,第一个参数表示当前方法或变量,第二个参数表示目标方法或变量,因为这个指令会破坏系统和包的模块化,因此在使用时必须导入unsafe
注意点
go:linkname
可以跨包使用,需要导入 unsafe
包。go build
无法编译go:linkname
,必须用单独的compile
命令进行编译,因为go build
会加上-complete
参数,这个参数会检查到没有方法体的方法,并且不通过,报错missing function body
。或者,在这个没有方法体的包下添加一个空的aa.s
的汇编文件标示,就可以编译了。这个指令不经常用,最好也不要用
,但理解这个指令可以帮助你理解核心包的很多代码。在标准库中是为了可以使用另一个包的unexported的方法或者变量,在敲代码的时候是不可包外访问的,但是运行时用这个命令hack了一下,就变得可以访问。
最大的作用就是定向可访问。
go-ole
hello
hello.s
hello.go
link
link.go
demo.go
go.mod
// hello.go
// 要导入 link 包
// 一般需要一个可导出的方法,例如 CallHello
package hello
import (
_ "go-ole/link"
)
func hello()
func CallHello() {
hello()
}
// link.go
// 需要导入 unsafe 包
package link
import _ "unsafe"
//go:linkname helloWorld go-ole/hello.hello
func helloWorld() {
println("hello world!")
}
// demo.go
package main
import (
"go-ole/hello"
)
func main() {
hello.CallHello()
}
go build demo.go
demo.exe