返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

plugin 1.18

发布于 2024-10-12 19:15:53 字数 3927 浏览 0 评论 0 收藏 0

简单的动态库(.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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文