设为首页 加入收藏

TOP

Golang 动态脚本调研(一)
2023-07-23 13:27:10 】 浏览:104
Tags:Golang

一、技术背景

1.1 程序的动态链接技术

在实际开发过程中,我们经常需要动态地更新程序的功能,或者在不变更程序主体文件的情况下添加或者更新程序模块。

1.1.1 动态链接库

首先最常见的是windows平台所支持的动态链接库(Dynamic Link Library),一般后缀名为.dll 。其优势非常明显:

  1. 多个程序可以共享代码和数据。即多个程序加载同一个DLL文件。
  2. 可以自然地将程序划分为若干个模块。每个模块输出为单独的DLL文件,由主程序加载执行。
  3. 跨语言调用。由于DLL文件是语言无关的,一个DLL文件可以被多种编程语言加载执行。
  4. 便于更新。在程序更新过程中,仅更新对应模块的DLL文件即可,无需重新部署整个程序。
  5. 为热更新提供技术可能性。动态链接库可以通过编程手段实现加载和卸载,以此可以支持不重启程序的情况下更新模块。
  6. 为程序提供编程接口。可以将自己程序的调用接口封装为DLL文件,供其他程序调用。

1.1.2 动态共享对象

在Linux平台,此项技术名为动态共享对象(dynamic shared objects),常见后缀名为.so

动态共享对象除了上述“动态链接库”的优势之外,也能解决由于Linux的开放性带来的底层接口兼容问题。即通过动态共享对象封装操作系统底层接口,对外提供统一的调用接口,以供上层应用程序调用。相当于提供了一层兼容层。

1.1.3 非编译语言的动态技术

非编译语言,由于本身是通过源代码发布,所以实现动态加载程序模块或者更新模块,直接修改源代码即可。思路简单且容易实现。

1.2 Golang 的动态技术

Golang作为编译型的开发语言,本身并不支持通过源代码实现动态加载和更新。但Golang官方提供了Plugin技术,实现动态加载。

通过在编译时添加参数,将Go程序编译为 Plugin:

go build -buildmode=plugin

但是此技术在当前版本(1.19)局限性非常大。通过其文档 https://pkg.go.dev/plugin 可知:

  1. 平台限制,目前仅支持:Linux, FreeBSD 和 macOS
  2. 卸载限制,仅支持动态加载,不支持动态卸载。
  3. 不提供统一接口,只能通过反射处理Plugin内部的属性和函数。

并且上述问题,Golang官方并不打算解决……

二、Golang 的第三方解释器(Yaegi)

解释器一般只存在于脚本语言中,但是Traefik为了实现动态加载的插件功能,开发了一个Golang的解释器。提供了在运行时直接执行Golang源代码的能力。

参考项目:https://github.com/traefik/yaegi

image

2.1 使用场景

yaegi 项目官方推荐三种场景:

  1. 内嵌解释器
  2. 动态扩展框架
  3. 命令行解释器

并且官方针对上述三种场景,均给出了相应的示例:

2.1.1 内嵌解释器

package main

import (
    "github.com/traefik/yaegi/interp"
    "github.com/traefik/yaegi/stdlib"
)

func main() {
    i := interp.New(interp.Options{})

    i.Use(stdlib.Symbols)

    _, err := i.eva l(`import "fmt"`)
    if err != nil {
        panic(err)
    }

    _, err = i.eva l(`fmt.Println("Hello Yaegi")`)
    if err != nil {
        panic(err)
    }
}

2.1.2 动态扩展框架

package main

import "github.com/traefik/yaegi/interp"

const src = `package foo
func Bar(s string) string { return s + "-Foo" }`

func main() {
    i := interp.New(interp.Options{})

    _, err := i.eva l(src)
    if err != nil {
        panic(err)
    }

    v, err := i.eva l("foo.Bar")
    if err != nil {
        panic(err)
    }

    bar := v.Interface().(func(string) string)

    r := bar("Kung")
    println(r)
}

2.1.3 命令行解释器

Yaegi提供了一个命令行工具,实现了 读取-执行-显示 的循环。

$ yaegi
> 1 + 2
3
> import "fmt"
> fmt.Println("Hello World")
Hello World
>

2.2 数据交互

数据交互方式比较多,需要注意的是从解释器内部返回的数据都是 reflect.Value 类型,获取其实际的值需要类型转换。

2.2.1 数据输入

可以有(但不限于)下述四种方法:

  1. 通过 os.Args 传入数据
  2. 通过 环境变量 传入数据
  3. 通过 赋值语句 传入数据
  4. 通过 函数调用 传入数据

下面是我自己写的代码示例:

package main

import (
	"fmt"

	"github.com/traefik/yaegi/interp"
	"github.com/traefik/yaegi/stdlib"
)
func main() {
	{ // 通过 os.Args 传入数据
		i := interp.New(interp.Options{
			Args: []string{"666"},
		})
		i.Use(stdlib.Symbols)
		i.eva l(`import "fmt"`)
		i.eva l(`import "os"`)
		i.eva l(`fmt.Printf("os.Args[0] --- %s\n", os.Args[0])`) 
               // os.Args[0] --- 666
	}
	{ // 通过 环境变量 传入数据
		i := interp.New(interp.Options{
			Env: []string{"inputEnv=666"},
		})
		i.Use(stdlib.Symbols)
		i.eva l(`import "fmt"`)
		i.eva l(`import "os"`)
		i.eva l(`fmt.Printf("os.Getenv(\"inputEnv\") --- %s\n", os.Getenv("inputEnv"))`)
               // os.Getenv("inputEnv") --- 666
	}
	{ // 执行赋值语句传入数据
		i := interp.New(interp.Options{})
		i.Use(stdlib.S
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Golang标准库之bytes介绍 下一篇我的设计模式之旅、07 观察者模式

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目