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值。
「常用基础的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处。栈前后变化的对比图:
调用insert(2)
后,栈顶值被弹出并插入到索引2处,原来位于索引2、3、4处的值则分别上移一个位置。栈前后变化的对比图:
调用rotate(2,1)
后,将索引2、3、4、5处的4个值朝栈顶方向旋转1个位置。栈前后变化的对比图:
-
String typeName(LuaType tp)
:把给定Lua类型转换成对应的字符串表示 -
LuaType type(int idx)
:根据索引返回值的类型,如果索引无效,则返回LuaType.luaNone
-
isXX
:提供了一系列以is
为前缀开头的方法,用于判断给定索引处的值是否属于该类型 -
bool toBoolean(int idx)
:从指定索引处取出一个布尔值,如果值不是布尔类型,则会进行类型转换。只有false
和nil
表示假,其他一切值都表示真 -
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(0, 0);
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(0, 0, 0);
}
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(0, 0);
}
要注意,以上示例只是一个简单演示,此处pushDartFunction
将注册一个全局函数randomInt
,当运行时加载完成之后,在Lua侧,randomInt
函数全局可用。实际开发中这不是很可取的,主要存在两个问题,第一是存在函数命名冲突的可能,第二个是需要用Dart为Lua编写大量扩展时,可能导致全局作用域混乱,降低性能。譬如,如果我们想为Flutter的接口编写Lua包装,那么可能需要一次注册的函数成百上千,全部注册到全局作用域显然不可取,不利于Lua侧代码的模块化管理。
解决以上问题的思路也很简单,模仿Lua标准库的实现即可,首先根据不同的模块创建不同的表,然后将函数注册到表中即可,Lua中的表就是一种作用域。具体请参考 flutter_lua_dardo[2]
您的点赞支持,就是我的最大动力!
或关注博主的视频网校
Reference
LuaLuaDardo:https://github.com/arcticfox1919/LuaDardo
[2]flutter_lua_dardo:https://github.com/arcticfox1919/flutter_lua_dardo
以上是关于LuaDardo中Dart与Lua的相互调用的主要内容,如果未能解决你的问题,请参考以下文章