當前位置: 妍妍網 > 碼農

Java 與 lua 互相呼叫簡單教程!

2024-05-28碼農

點選關註公眾號,Java幹貨 及時送達 👇

在某些業務場景下,我們可能會遇到 lua 中要呼叫 java 程式碼情況,當然這個用 JNI 肯定是可以做到的,但是有更加方便的辦法:LuaJavaBridge(LuaJava)和 LuaJ。

luaj 主要特征

  • 可以從 Lua 呼叫 Java class Static Method

  • 呼叫 Java 方法時,支持 int/float/boolean/String/Lua function 五種參數型別

  • 可以將 Lua function 作為參數傳遞給 Java,並讓 Java 保存 Lua function 的參照

  • 可以從 Java 呼叫 Lua 的全域函式,或者呼叫參照指向的 Lua function

  • luaj 的功能很簡單,但對於整合各種 SDK 來說已經完全滿足需求了。

    luaj 用法範例

    Java 方法原型:

    public static float getNum(float n) {
    return n;
    }

    lua 呼叫範例:

    -- Java 類的名稱
    local className = "com/xttblog/Test"
    -- 呼叫 的Java 方法名
    local method = 'getNum'
    -- 呼叫 Java 方法需要的參數
    local n = 10
    local args = {
    n
    }
    -- 呼叫 Java 方法
    local _, testStaticMethod = luaj.callStaticMethod( className, method, args)

    luaj 實作原理

    luaj 的核心目標有兩個:從 Lua 呼叫 Java, 從 Java 呼叫 Lua。整理出來就是如下幾點

  • 尋找並呼叫指定的 Java 方法

  • 檢查呼叫結果,並從 Java 方法獲取返回值

  • 將 Lua function 作為參數傳遞給 Java 方法

  • 在 Java 方法中呼叫 Lua function

  • 尋找並呼叫指定的 Java 方法

    JNI 提供了 Find class() 方法用於尋找指定的 class,所以 luaj.callStaticMethod() 的第一個參數就是要呼叫的 Java class 的完整類名稱(類名稱中的「.」要替換為「/」)

    找到指定 class 後,利用 JNI 的 GetStaticMethodID() 方法就可以找到這個類的指定靜態方法,前提是要提供靜態方法的名稱和簽名。

    所謂簽名,就是指Java方法的參數型別和返回型別定義。方法的簽名就是類似 (Ljava/lang/String;ZZI)V 這樣的一串描述,透過字節碼方式可以檢視,如下範例:

    圖片

    關於 Java 方法簽名的具體定義,可以參考: JNI Type Signatures

    這裏要說的是 luaj 可以根據呼叫參數自動猜測方法簽名所以範例中我們並沒有寫簽名。

    範例中指定參數:

    local args = {n}

    luaj 根據這 個參數,會構造出正確的方法簽名。

    註意:這裏要說的是 Lua 裏沒有辦法準確判斷一個數值是整數還是浮點數,所以 luaj 在猜測方法簽名時,假定所有的數值都是浮點數。所以下面呼叫會報錯:

    public static int getNum(int n) {
    return n;
    }
    -- Java 類的名稱
    local className = "com/xttblog/Test"
    -- 呼叫 的Java 方法名
    local method = 'getNum'
    -- 呼叫 Java 方法需要的參數
    local n = 10
    local args = {
    n
    }
    -- 呼叫 Java 方法
    local _, testStaticMethod = luaj.callStaticMethod( className, method, args)

    這樣是不行的,所以這個時候我們要自己定義簽名。

    下面給出正確的範例

    public static int getNum(int n) {
    return n;
    }
    -- Java 類的名稱
    local className = "com/xttblog/Test"
    -- 呼叫 的Java 方法名
    local method = 'getNum'
    -- 呼叫 Java 方法需要的參數
    local n = 10
    local args = {
    n
    }
    -- 定義簽名-- 參數: [I]nteger-- 返回值: [I]nt
    local sig = "(I)I"
    -- 呼叫 Java 方法
    local _, testStaticMethod = luaj.callStaticMethod( className, method, args,sig)

    簽名使用「(依次排列的參數型別)返回值型別」的格式,幾個例子如下:

    簽名 解釋
    ()V 參數:無,返回值:無
    (I)V 參數:int,返回值:無
    (Ljava/lang/String;)Z 參數:字串,返回值:布爾值
    (IF)Ljava/lang/String; 參數:整數、浮點數,返回值:字串

    這裏列出不同型別對應的 Java 簽名字串:

    型別名 型別
    I 整數,或者 Lua function
    F 浮點數
    Z 布爾值
    Ljava/lang/String; 字串
    V Void 空,僅用於指定一個 Java 方法不返回任何值

    Java 方法裏接收 Lua function 的參數必須定義為 int 型別。

    從 Java 方法獲取返回值

    luaj 會檢查呼叫結果,並從 Java 方法獲取返回值。

    luaj 呼叫 Java 方法時,可能會出現各種錯誤,因此 luaj 提供了一種機制讓 Lua 呼叫程式碼可以確定 Java 方法是否成功呼叫。

    luaj.callStaticMethod() 會返回兩個值:

  • 當成功時,第一個值為 true,第二個值是 Java 方法的返回值(如果有)。

  • 當失敗時,第一個值為 false,第二個值是錯誤程式碼。

  • 下面的程式碼展示了如何檢查返回結果和獲得返回值:

    public static int AddTwoNumbers(final int number1, final int number2) {
    return number1 + number2;
    }

    Lua程式碼

    local args = {2, 3}
    local sig = "(II)I"
    local ok, ret = luaj.callStaticMethod( className, "AddTwoNumbers", args, sig)
    if not ok then
    print("luaj error:", ret)
    else
    print("ret:", ret) -- 輸出 ret: 5
    end

    錯誤程式碼定義如下:

    錯誤程式碼 描述
    -1 不支持的參數型別或返回值型別
    -2 無效的簽名
    -3 沒有找到指定的方法
    -4 Java 方法執行時丟擲了異常
    -5 Java 虛擬機器出錯
    -6 Java 虛擬機器出錯

    將 Lua function 作為參數傳遞給 Java 方法

    Lua 虛擬機器中,Lua function 以值的形式保存。但這個值無法直接給 Java 用,所以 luaj 做了一個 Lua function 參照表。當一個 Lua function 傳遞給 Java 時,這個 function 對應的值會被存在參照表中,並獲得一個唯一的參照 ID (整數)。Java 程式碼拿到這個參照 ID 後,就可以很方便的呼叫該 Lua function 了。

    所以 Java 方法裏接收 Lua function 的參數必須定義為 int 型別。

    範例:

    public static int getNum(int n) {
    return n;
    }
    localfunction callback(result)
    ---方法內容
    end
    -- Java 類的名稱
    local className = "com/xttblog/Test"
    -- 呼叫 的Java 方法名
    local method = 'getNum'
    -- 呼叫 Java 方法需要的參數
    local args = {
    callback
    }
    -- 定義簽名-- 參數: [I]nteger-- 返回值: [I]nt
    local sig = "(I)I"
    -- 呼叫 Java 方法
    local _, testStaticMethod = luaj.callStaticMethod( className, method, args,sig)

    另外,LuaJ 也很好用。只需引入 pom。


    ‍然後直接把 lua 程式碼當做 String 字串內嵌到 Java 程式碼中:

    String luaStr = "print 'hello,world!'";
    Globals globals = JsePlatform.standardGlobals();
    LuaValue chunk = globals.load(luaStr);
    chunk.call();

    也可以建立一個 login.lua 指令碼,內容如下:

    --無參函式
    function hello()
    print'hello'
    end
    --帶參函式
    functiontest(str)
    print('data from java is:'..str)
    return'haha'
    end

    然後,Java先載入login.lua指令碼並編譯,然後再獲取指定名稱的函式,無參的直接使用call()方法呼叫,帶參的需要透過invoke(LuaValue[])傳入參數列:

    String luaPath = "res/lua/login.lua"; //lua指令碼檔所在路徑
    Globals globals = JsePlatform.standardGlobals();
    //載入指令碼檔login.lua,並編譯
    globals.loadfile(luaPath).call();
    //獲取無參函式hello
    LuaValue func = globals.get(LuaValue.valueOf("hello"));
    //執行hello方法
    func.call();
    //獲取帶參函式test
    LuaValue func1 = globals.get(LuaValue.valueOf("test"));
    //執行test方法,傳入String型別的參數參數
    String data = func1.call(LuaValue.valueOf("I'am from Java!")).toString();
    //打印lua函式回傳的數據
    Logger.info("data return from lua is:"+data);

    執行結果如下:

    hello
    data from java is:I'am from Java!
    八月 07, 2022 5:31:25 下午 com.tw.login.tools.Logger info
    資訊: lua return data:haha


    END


    看完本文有收獲?請轉發分享給更多人

    關註「Java編程鴨」,提升Java技能

    關註Java編程鴨微信公眾號,後台回復:碼農大禮包可以獲取最新整理的技術資料一份。涵蓋Java 框架學習、架構師學習等!

    文章有幫助的話,在看,轉發吧。

    謝謝支持喲 (*^__^*)