背景紹介
Golang の関数名は&
ポインタを使用できないため、直接使用することはできませんunsafe.Pointer
。そのため、関数をキャストしたり、シグネチャを持つ関数をパラメータとして使用したりすることはできません。
問題分析
しかし、fmt.Print
一連の関数が任意の関数を入力パラメーターとして受け取り、そのポインターを出力できることはわかっています。そこで、突破口としてソースコードを解析してみると解決策が見つかるかもしれません。
ソースコードを解析した結果、 Golangではどのような種類のinterface{}
変数もインターフェースを実装しているとみなせるため、関数名をインターフェースに変換することでアドレスを取得できる変数を取得できることが分かりました。パッケージはこの変数を通じてその型、つまり渡される関数の型を取得できる
ため、この変数を単なる関数ポインターにすることはできません。reflect
したがって、変数は次の構造に適用されます。
// 没有方法的interface
type eface struct {
_type uintptr
data unsafe.Pointer
}
ここのフィールドdata
は、必要な関数ポインターです。これを取得したらunsafe.Pointer
、キャストを介して関数変数にコピーできます。この関数変数の型は渡される型と同じである必要はないため、関数の必須の型変換を実現できます。
サンプルコード
ここでは、パッケージを使用して をplugin
生成しplugin.so
、メイン関数がプラグインをロードして独自のCalled
関数ポインタをプラグインに渡し、プラグインは関数ポインタを独自の関数変数に保存してから、mainfun
メイン関数Call
がプラグインのメソッド。mainfun
メイン関数でプラグイン呼び出し関数を実現するために関数ポインターを呼び出します。
メイン関数 main.go
package main
import (
"fmt"
"plugin"
)
func Called() {
fmt.Println("main func is called!")
}
func main() {
h, err := plugin.Open("plugin/plugin.so")
var initfun, callfun plugin.Symbol
if err == nil {
initfun, err = h.Lookup("Inita")
if err == nil {
callfun, err = h.Lookup("Call")
if err == nil {
fmt.Println("addr from main:", Called)
initfun.(func(interface{
}))(Called)
Called()
callfun.(func())()
}
}
}
if err != nil {
fmt.Println(err)
}
}
プラグイン-プラグイン/plugin.go
package main
import (
"fmt"
"reflect"
"unsafe"
)
var mainfun func()
// 没有方法的interface
type eface struct {
_type uintptr
data unsafe.Pointer
}
func Inita(ptr interface{
}) {
fmt.Println("addr val of ptr:", ptr)
fmt.Printf("addr val of unsafe: 0x%x\n", reflect.ValueOf(ptr).Pointer())
f := (*eface)(unsafe.Pointer(&ptr)).data
mainfun = *(*(func()))(unsafe.Pointer(&f))
fmt.Println("addr from plug:", mainfun)
}
func Call() {
mainfun()
}
次のようにコンパイルして実行します。
go build -ldflags "-s -w" -o main
cd plugin
go build -buildmode=plugin -ldflags "-s -w"
cd ..
./main
出力は次のとおりです
addr from main: 0x40cdc40
addr val of ptr: 0x40cdc40
addr val of unsafe: 0x40cdc40
addr from plug: 0x40cdc40
main func is called!
main func is called!