上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
plugin 1.18
简单的动态库(.so)装载和查找符号。
以示例说明:
test/ | +-- main.go | +-- mylib/ | +-- add.go
// mylib/add.go package mylib func Add(x, y int) int { return x + y }
// main.go package main import ( "test/mylib" ) func main() { println(mylib.Add(11, 22)) }
将 mylib
子包改成插件(plugin)模式。
- 将
mylib
初始化为独立模块,从test
里排除。
- 将
package mylib
改成package main
。 - 添加一些导出和非导出成员,用于测试。
- 以
-buildmode=plugin
方式编译。
// add.go package main // <--- !!!! var X = 100 const S = "abc" func init() { println("plugin init.") } func hello() { println("hello, world!") } func Add(x, y int) int { return x + y } func main() { println("plugin.main.") }
$ cd mylib $ go mod init mylib $ go build -buildmode=plugin $ nm mylib.so | grep mylib 000000000007f500 T mylib.Add 000000000007f4a0 T mylib.init.0 00000000000e2fc0 D mylib..inittask 000000000007f520 T mylib.main 00000000000e2f68 D mylib.X
接下来,修改 main.go
,动态装载和调用。
package main import ( "plugin" "log" ) func test() { p, err := plugin.Open("./mylib/mylib.so") if err != nil { log.Fatalln(err) } // Add ---------------------------------- s, err := p.Lookup("Add") if err != nil { log.Fatalln(err) } add, ok := s.(func(int, int) int) if ok { println(add(11, 22)) } // 33 // X ------------------------------------- s, err = p.Lookup("X") if err != nil { log.Fatalln(err) } x := s.(*int) println(*x) // 100 // hello --------------------------------- s, err = p.Lookup("hello") if err != nil { log.Fatalln(err) } } func main() { test() // test() }
$ go build -o test && ./test plugin init. 33 100 plugin: symbol hello not found in plugin mylib
- 初始化函数(
init
)正常执行,且仅执行一次。 - 入口函数(
main
)未执行。 - 非导出成员不可用。
源码剖析
利用 CGO 调用 dlopen
实现,和在 C 里实现类似功能一致。
// plugin.go func Open(path string) (*Plugin, error) { return open(path) } func (p *Plugin) Lookup(symName string) (Symbol, error) { return lookup(p, symName) }
// plugin_dlopen.go /* #cgo linux LDFLAGS: -ldl #include <dlfcn.h> #include <limits.h> #include <stdlib.h> #include <stdint.h> #include <stdio.h> static uintptr_t pluginOpen(const char* path, char** err) { void* h = dlopen(path, RTLD_NOW|RTLD_GLOBAL); if (h == NULL) { *err = (char*)dlerror(); } return (uintptr_t)h; } static void* pluginLookup(uintptr_t h, const char* name, char** err) { void* r = dlsym((void*)h, name); if (r == NULL) { *err = (char*)dlerror(); } return r; } */ import "C" var ( pluginsMu sync.Mutex plugins map[string]*Plugin ) func open(name string) (*Plugin, error) { pluginsMu.Lock() // 避免重复装载。 if p := plugins[filepath]; p != nil { pluginsMu.Unlock() <-p.loaded // 等待正在装载的行为结束。 return p, nil } h := C.pluginOpen((*C.char)(unsafe.Pointer(&cPath[0])), &cErr) // 全局字典。 if plugins == nil { plugins = make(map[string]*Plugin) } // 插件信息(符号表)。 pluginpath, syms, errstr := lastmoduleinit() // 将插件对象存入全局字典。 p := &Plugin{ pluginpath: pluginpath, loaded: make(chan struct{}), } plugins[filepath] = p pluginsMu.Unlock() // 执行初始化函数。(和运行时的做法相同) initTask := C.pluginLookup(h, ...) if initTask != nil { doInit(initTask) } // 符号表。 updatedSyms := map[string]any{} for symName, sym := range syms { ... updatedSyms[symName] = sym } p.syms = updatedSyms // 装载结束。 close(p.loaded) return p, nil } func lookup(p *Plugin, symName string) (Symbol, error) { if s := p.syms[symName]; s != nil { return s, nil } return nil, errors.New("plugin: ... " + p.pluginpath) }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论