LuaDardo中Dart与Lua的相互调用

Posted 编程之路从0到1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LuaDardo中Dart与Lua的相互调用相关的知识,希望对你有一定的参考价值。

本系列相关文章:


Lua C API

Lua C API主要是指一系列以lua_开头的C语言函数(也可能是宏函数)。

Lua的定位是一门强大、高效、轻量级的可嵌入脚本语言。为了很方便地嵌入到其他宿主(Host)环境中,Lua核心是以库(Library)的形式被实现的,其他应用程序只需要链接Lua库就可以使用Lua提供的API轻松获得脚本执行能力。

Lua 3.1引入了lua_State结构体,对解释器状态进行了封装,Lua 4.0引入了虚拟栈的概念,但lua_State结构体仍属于实现细节,用户只需要使用lua_newstate()函数创建lua_State实例,其他函数则用于操作lua_State实例。

除了提供lua_开头的基本函数外,Lua还提供了约60个以luaL_开头的辅助函数。辅助函数完全是在基本函数之上进行的封装,目的在于提供一些更简单方便的操作。

Lua State是Lua API最核心的概念,全部的API函数都是围绕Lua State进行操作,而Lua State内部封装了一个基础的虚拟栈(可称为Lua栈)。Lua栈是宿主语言(对于Lua官方虚拟机来说是C语言,对于LuaDardo库来说就是Dart语言)和Lua语言进行沟通的桥梁,Lua API函数有很大一部分是专门用来操作Lua栈的。

「在LuaLuaDardo[1]库中,基本上兼容了大部分Lua C API,只在命名上做了Dart语言规范的适配。因此,要学习Dart与Lua的相互调用,必须先熟悉Lua C API的相关知识。关于Lua C API的具体内容请阅读上一篇博客。」

注意,LuaDardo库基础API接口,请查看LuaBasicAPI类,它基本对应lua.h文件中的接口;辅助API接口,查看LuaAuxLib类,对应luaxlib.h文件接口。

Lua栈

「Lua栈操作有一些需要注意的地方:」

  • 在Lua API里,栈索引是从1开始的
  • 栈索引可以是负数。正数索引叫作 「绝对索引」,从1(栈底)开始递增,负数索引叫作 「相对索引」,从-1(栈顶)开始递减。Lua API函数会在内部把相对索引转换为绝对索引
  • 若栈的容量是n,栈顶索引是top(0 < top <= n)。则称位于 [1, top]闭区间内的索引为 「有效(Valid)索引」,位于 [1, n]闭区间内的索引为 「可接受(Acceptable)索引」。如果要往栈中写值,必须提供有效索引,否则可能导致错误甚至崩溃。如果仅从栈中读取值,则可提供可接受索引;对于无效的可接受索引,其行为相当于该索引处存放的是nil值。
LuaDardo中Dart与Lua的相互调用

「常用基础的LuaLuaDardo API:」

  • int getTop():返回栈顶索引
  • int absIndex(int idx):把索引转换为绝对索引
  • bool checkStack(int n):检查栈剩余空间,是否还可以压入n个值而不会导致溢出(由于Dart语言栈自动增长,在LuaDardo中该方法永远返回 true
  • void pop(int n):从栈顶弹出n个值
  • void copy(int fromIdx, int toIdx):把值从一个位置复制到另一个位置
  • void pushValue(int idx):把指定索引处的值推入栈顶
  • void replace(int idx):将栈顶值弹出,然后写入指定位置
  • void insert(int idx):将栈顶值弹出,然后插入指定位置
  • void remove(int idx):删除指定索引处的值,然后将该值上面的值全部下移一个位置
  • void rotate(int idx, int n):将 [idx, top]索引区间内的值朝栈顶方向旋转n个位置
  • void setTop(int idx):将栈顶索引设置为指定值。如果指定值小于当前栈顶索引,相当于弹出操作(指定值为0相当于清空栈);如果指定值大于当前栈顶索引,则相当于压入多个 nil

这里对几个方法举例说明

调用replace(2)后,栈顶值被弹出并且写入到索引2处。栈前后变化的对比图:

LuaDardo中Dart与Lua的相互调用

调用insert(2)后,栈顶值被弹出并插入到索引2处,原来位于索引2、3、4处的值则分别上移一个位置。栈前后变化的对比图:

LuaDardo中Dart与Lua的相互调用

调用rotate(2,1)后,将索引2、3、4、5处的4个值朝栈顶方向旋转1个位置。栈前后变化的对比图:

LuaDardo中Dart与Lua的相互调用
  • String typeName(LuaType tp):把给定Lua类型转换成对应的字符串表示
  • LuaType type(int idx):根据索引返回值的类型,如果索引无效,则返回 LuaType.luaNone
  • isXX:提供了一系列以 is为前缀开头的方法,用于判断给定索引处的值是否属于该类型
  • bool toBoolean(int idx):从指定索引处取出一个布尔值,如果值不是布尔类型,则会进行类型转换。只有 falsenil表示假,其他一切值都表示真
  • double toNumber(int idx)double toNumberX(int idx):从指定索引处取出一个数字,如果值不是数字类型,则需要进行类型转换。区别是:如果值不是数字类型并且也无法转换成数字类型,前者只是简单地返回0,后者则会在转换失败时返回 null
  • int toInteger(int idx)int toIntegerX(int idx):从指定索引处取出一个整数值,如果值不是整数类型,则需要进行类型转换
  • String toStr(int idx):从指定索引处取出一个值,如果值是字符串,则返回该字符串。如果值是数字,则将值转换为字符串,转换失败时返回 null。(注,原C API中该接口写作 tostring,因与Dart类的方法名冲突,改为 toStr

此外,还有一系列以push为前缀开头的常用方法,将具体类型的值压入栈中。基本上类似于C API,请参考一文

创建运行时

LuaState state = LuaState.newState();
// 加载标准库
state.openLibs();
// 加载Lua代码
state.loadString("print('hello')");
state.call(00);

Dart调Lua

获取变量

-- test.lua
a = 100
b = 120

dart代码

LuaState ls = LuaState.newState();
ls.openLibs();
ls.doFile("test.lua");

// a 入栈
ls.getGlobal("a");
if(ls.isNumber(-1)){
    var a = ls.toNumber(-1);
    print("a=$a");
}
// b 入栈
ls.getGlobal("b");
if(ls.isNumber(-1)){
    var b = ls.toNumber(-1);
    print("b=$b");
}

「dart获取lua全局table」

-- test.lua
mytable = {k1 = 1, k2 = 2.34, k3 = "test"}

dart代码

    ls.getGlobal("mytable");
    // 压入lua表的一个key名称
    ls.pushString("k1");
    // 将栈顶的key名称弹出,得到对应key的值,并将结果压入栈顶
    ls.getTable(-2);

    if(ls.isInteger(-1)){
      // 获得键k1的值
      var k1 = ls.toInteger(-1);
    }

    // 重复以上过程
    ls.pushString("k2");
    ls.getTable(-2);
    
    if(ls.isNumber(-1)){
      var k2 = ls.toNumber(-1);
    }

此外,还提供了getField方法,替代上面的方式,直接获取表的键值,

    ls.getGlobal("mytable");
    ls.getField(-1"k1");
    if(ls.isInteger(-1)){
      var k1 = ls.toInteger(-1);
    }

调用lua函数

-- test.lua

function myFunc()
    print("myFunc run")
end

dart代码

ls.doFile("test.lua");

ls.getGlobal("myFunc");
if(ls.isFunction(-1)){
    ls.pCall(000);
}

pCall方法有三个参数,第一个参数表示被调用的Lua函数的参数个数,第二个参数表示被调用的Lua函数的返回值的个数,因为Lua的函数支持多个返回值,所以返回值也可能有多个。

需要注意,「被调用的Lua函数会被压入栈,该函数的参数也会按从左往右的顺序压入栈。该函数及其所有参数在被执行后会从栈中弹出。函数的返回值将会在函数返回的时候按从左往右的顺序压入栈中。」

Lua调Dart

获取变量

dart代码

// 值入栈
ls.pushString("张三");
// 设置变量名
ls.setGlobal("name");

lua代码

-- 获取全局变量 name
print(name) -- 张三

「Lua获取Dart中定义的全局table」

dart代码

    // 创建一个table并压入栈中
    ls.newTable();
    // 压入一个key
    ls.pushString("name");
    // 压入key对应的值。注意,此时table在栈中的索引变为-3
    ls.pushString("Bruce");
    // 将上面的键值对设置到table中,并弹出键和值(此时table对应的索引是-3)
    ls.setTable(-3);
    // 给table设置变量名,并弹出table
    ls.setGlobal("person");

lua代码

-- 相当于定义了一个表:person = {name="Bruce"}
print(person.name)

关于table的操作,还有两个方法:

  • rawSet(idx):相当于 setTable(idx),但是只做一次直接访问(不触发元方法),速度更快。
  • rawGet(idx):同上,相当于 getTable(idx)

调用Dart函数

这里以调用Dart语言的随机数生成器为例,编写一个包装函数,包装nextInt方法,该方法原型为int nextInt(int max),返回值是一个0~max之间的随机整数。

import 'package:lua_dardo/lua.dart';
import 'dart:math';

// 包装函数须符合函数签名:int Function(LuaState ls)
int randomInt(LuaState ls) {
  int max = ls.checkInteger(1);
  ls.pop(1);

  var random = Random();
  var randVal = random.nextInt(max);
  ls.pushInteger(randVal);
  return 1;
}

void main(List<String> arguments) {
  LuaState state = LuaState.newState();
  state.openLibs();

  state.pushDartFunction(randomInt);
  state.setGlobal('randomInt');

  // 执行一段Lua脚本代码,测试randomInt函数
  state.loadString('''
rand_val = randomInt(10)
print('random value is '..rand_val)
'''
);
  state.call(00);
}

要注意,以上示例只是一个简单演示,此处pushDartFunction将注册一个全局函数randomInt,当运行时加载完成之后,在Lua侧,randomInt函数全局可用。实际开发中这不是很可取的,主要存在两个问题,第一是存在函数命名冲突的可能,第二个是需要用Dart为Lua编写大量扩展时,可能导致全局作用域混乱,降低性能。譬如,如果我们想为Flutter的接口编写Lua包装,那么可能需要一次注册的函数成百上千,全部注册到全局作用域显然不可取,不利于Lua侧代码的模块化管理。

解决以上问题的思路也很简单,模仿Lua标准库的实现即可,首先根据不同的模块创建不同的表,然后将函数注册到表中即可,Lua中的表就是一种作用域。具体请参考 flutter_lua_dardo[2]


您的点赞支持,就是我的最大动力!

编程之路从0到1

或关注博主的视频网校

云课堂

Reference

[1]

LuaLuaDardo:https://github.com/arcticfox1919/LuaDardo

[2]

flutter_lua_dardo:https://github.com/arcticfox1919/flutter_lua_dardo

以上是关于LuaDardo中Dart与Lua的相互调用的主要内容,如果未能解决你的问题,请参考以下文章

Lua 15分钟快速上手(上)

第十七章 C++与Lua的相互绑定之ELuna

C和Lua之间的相互调用

qt 环境下,Lua 与 QsciScintilla 库的使用

Unity中C#与Lua的交互

lua C API