github.com/yuin/gopher-lua 踩坑日記

2023-09-01 12:00:58

本文主要記錄下在日常開發過程中, 使用 github.com/yuin/gopher-lua 過程中需要注意的地方。

後續遇到其他的需要注意的事項再補充。

1、載入LUA_PATH環境變數

在實際開發中,我們會將一些公共的、可重複使用的程式碼封裝起來,假如我們只是一些簡單的處理,全部寫在一個檔案是沒有問題的,維護起來也並不是很麻煩。但是當我們的需求變得複雜起來,或者需求調整的時候,我們還是將所有功能都寫在同一個檔案的時候,就變得不合理起來,後期的維護更是災難性的,這個時候就需要將一些公共的、重複使用的程式碼,抽離出來,根據功能的不同分類,當做一個個模組,在使用的時候使用 require 匯入進來,這樣才合理。

既然我們需要將自定義的 module 匯入進來,那麼我們肯定是需要設計環境變數的。

最重要的地方就是下面這點:

// 重點就是這句, 先設定環境變數, 再建立 lua 的 虛擬機器器, 這樣環境變數才生效!!!
setLuaLibPath()
lvm := newLuaVm(pluginPath, context.Background())

接下來再詳細看演示的demo。

先看看程式碼目錄分層,有一個整體認識。

--go_call_lua_test
|---go.mod
|---main.go
|---plugin.lua
|---lib/lua/test.lua

main.go 程式碼

package main

import (
	"context"
	"errors"
	"fmt"
	lua "github.com/yuin/gopher-lua"
	"go.uber.org/atomic"
	luar "layeh.com/gopher-luar"
	"os"
	"path/filepath"
)

func getExeDir() (string, string) {
	exePath, _ := os.Executable()
	exeDir, exeName := filepath.Split(exePath)
	return exeDir, exeName
}

func absPath(fp string) string {
	exeAbsDir, _ := getExeDir()
	//絕對路徑
	if !filepath.IsAbs(fp) {
		return filepath.Join(exeAbsDir, fp)
	} else {
		return fp
	}
}

func setLuaLibPath() {
	//設定lua環境變數
	pathStr := ""
	LuaLibPath := []string{"./lib/lua/?.lua"}
	for _, path := range LuaLibPath {
		pathStr += ";" + absPath(path)
	}
	pathStr += ";;"
	os.Setenv("LUA_PATH", pathStr)
}

type LuaVm struct {
	*lua.LState
	path   string
	Loaded atomic.Bool
}

func newLuaVm(path string, ctx context.Context) *LuaVm {
	lv := &LuaVm{
		path:   path,
		LState: lua.NewState(),
	}
	lv.SetContext(ctx)
	if err := lv.load(); err != nil {
		panic(fmt.Errorf("lvm error=%s", err))
	}
	return lv
}

func (lv *LuaVm) load() error {
	//載入工具 下面是將 go 提供的功能當做模組 給 lua 使用
	//lv.SetGlobal("FILE", luar.New(lv.LState, &test.FileTool{}))

	if len(lv.path) == 0 {
		return fmt.Errorf("plugin file empty")
	}
	if err := lv.DoFile(lv.path); err != nil {
		return err
	}

	lv.Loaded.Store(true)
	return nil
}

func (lv *LuaVm) CallLua() (result string, err error) {

	if err := lv.CallByParam(lua.P{
		Fn:      lv.GetGlobal("PluginTest"),
		NRet:    2,
		Protect: true,
	}, luar.New(lv.LState, "I'm coming go!")); err != nil {
		fmt.Printf("PrimaryIn error=%s", err)
	}
	retCode, _ := lv.Get(-1).(lua.LNumber)
	lv.Pop(1)

	retPkt, _ := lv.Get(-1).(lua.LString)
	lv.Pop(1)

	if retCode != 0 {
		return "", errors.New("untreated")
	}

	return string(retPkt), nil
}

func main() {
	pluginPath := "./plugin.lua"

	// 重點就是這句, 先設定環境變數, 再建立 lua 的 虛擬機器器, 這樣環境變數才生效!!!
	setLuaLibPath()
	lvm := newLuaVm(pluginPath, context.Background())

	ret, err := lvm.CallLua()
	fmt.Println("ret --> ", ret)
	fmt.Println("err --> ", err)
}

plugin.lua 程式碼

local TestLib = require("test")

function PluginTest(param)
    print(param)
    print("coming PluginTest")
    TestLib.CallTestFunc()

    return "I'm coming lua!"
end

test.lua 程式碼

justTest = {}

function justTest.CallTestFunc()
    print("coming justTest.CallTestFunc")
end

-- 一定要 return, 否則不生效
return justTest

編譯後執行結果:

I'm coming go!
coming PluginTest
coming justTest.CallTestFunc
ret -->  I'm coming lua!    
err -->  <nil>