目前已有 luaj 项目可在JVM中执行Lua脚本,由于其编译效率和执行效率不如 luac ,所以开发了这个接口和luaj相似的、使用luac作为Lua虚拟机的Java库。
对于使用过luaj的朋友,初学luac一定会非常懵逼,因为luaj虚拟机做了很好的封装,所以使用或理解起来非常简单;而luac只给程序员提供了一个lua环境(lua_State)、操作lua堆栈的一堆函数和把lua数据类型转为C数据类型的函数。这篇文章重点讲述本项目 LuaJava 的实现原理和使用方法。
简单使用方法:
Globals globals = Globals.createLState(true); //创建Lua虚拟机
boolean result = globals.loadString("name", str);//加载lua源码
boolean result = globals.loadData("name", data); //加载lua源码或二进制码
if (result) result = globals.callLoadedData();//执行刚加载成功的lua脚本
代码分析
第一行,和luaj很像,创建Lua虚拟机,并获取全局表。在C层,通过lua函数创建虚拟机环境(lua_State);初始化lua自带库;初始化自定义的lua错误函数;在全局表中初始化一个存放Java Global引用的表(用来在虚拟机销毁的时候释放Global引用,后面会提到);在lua package.searchers
表中增加通过Java代码寻找对应lua文件函数(lua脚本require时会调用)。
第二行和第三行,作用相同,加载lua脚本源码或加载二进制码,若加载失败,可通过globals.getLoadState()
获取错误码,通过globals.getErrorMsg()
获取错误信息。在C层,通过lua函数加载脚本。
第四行,执行在此虚拟机中刚加载的脚本,若未加载脚本,或加载失败,则会抛出异常,若执行失败,可通过globals.getExecuteState()
获取错误码,通过globals.getErrorMsg()
获取错误信息。在C层,通过lua函数调用在栈顶的lua函数,并返回。
注册静态函数
Java代码:
globals.registerStaticBridgeSimple("LuaBridgeClass", BridgeA.class, "bridge1", "bridge2"...); //注册Java静态方法
lua代码:
LuaBridgeClass:bridge1(params1)--调用java中的BridgeA.bridge1
LuaBridgeClass:bridge2(params2)--调用java中的BridgeA.bridge2
代码分析
使用过luaj的同学应该知道,LuaBridgeClass:bridge1
在lua的语境中,实际上是在global表或注册表中寻找名称为LuaBridgeClass的table,再在此table中或其元表(metatable)中寻找名称为bridge1相关的函数,然后将参数传入并执行。
本项目中,原理相似,registerStaticBridgeSimple
在C层创建一个表,使用键值LuaBridgeClass放入到Global表中,表中每个键对应传入的方法名,值为lua的Closure;在Closure中,存放了相关java类(jclass)和相关方法(jmethodID),在虚拟机调用此Closure时,通过JNI接口调用到java对应的方法中,并将java返回的结果依次放入堆栈中返回。
由于jclass为非Globals引用,在函数结束时就不可用了,所以在这里会创建一个jclass的Globals引用,并将Globas引用放入到前文提到的存放Java Global引用表中。
注册Java对象
Java代码:
globals.registerUserdataSimple("LuaBridgeClass2", BridgeB.class, "bridge1", "bridge1"...); //注册Java对象
lua代码:
local ud = LuaBridgeClass2(initParams)--创建一个ud实例
ud:bridge1(params1)--调用java中的BridgeB.bridge1方法
ud:bridge2(params2)--调用java中的BridgeB.bridge2方法
代码分析
创建UD实例和调用静态Bridge相似,在Global表或注册表中寻找名称为LuaBridgeClass2的函数,执行这个函数,返回一个lua的userdata类型。函数中通过JNI接口创建一个java对象,将对象包装成lua可识别的userdata类型,设置元表,并返回;元表在注册时已生成,调用bridge方法,会通过JNI接口调用对应的java方法,并将java返回结果依次放入堆栈中。
java对象的内存由lua虚拟机的gc系统管理,当需要被gc时,会调用元表中__gc
对应方法,释放Global引用,并调用对应java类中的__onLuaGc
方法。
详细内容请参考项目 LuaJava 的实现。
网友评论