C#与Lua进行交互主要通过虚拟栈实现,栈的索引有两套机制,分为正数与负数,如果索引为正数,则1表示栈底,如果索引为负数,则-1表示栈顶。虚拟栈是Lua和C#之间交互的关键。Lua和C#都使用虚拟栈来保存函数的参数和返回值,通过压入和弹出栈顶元素来进行数据传递。中间语言是通过虚拟栈中的数据来执行代码的,因此虚拟栈的实现非常重要。
我们热更新使用的xlua是一个用于将Lua集成到C#中的工具,而LuaEnv是xLua中Lua虚拟机的一个封装类。
关于LuaEnv的get方法和set方法,它们的行为与Lua的全局变量读写操作类似。具体来说:
- get方法会将指定的全局变量名对应的值压入Lua虚拟栈中。这个值可以是任何类型,比如数字、字符串、表等等。如果全局变量不存在,get方法将会把nil值压入栈中。调用方可以通过读取栈顶元素来获取这个值。
- set方法会从Lua虚拟栈中弹出一个值,并将其赋给指定的全局变量名。如果全局变量不存在,则会创建一个新的全局变量并赋值。如果栈顶元素不是一个合法的Lua值,则会抛出一个异常。
C#调用Lua
- 过程:
- 由C#文件先调用Lua解析器底层DLL库(由C语言编写),再由DLL文件执行相应的Lua文件
- 由C#生成Bridge文件,Bridge调DLL文件,先调用Lua中DLL文件,由DLL文件执行DLL代码
- 详细过程:
- 首先,创建一个Lua虚拟机,并将需要调用的Lua文件加载到虚拟机中。
- 在Lua中注册需要被C#调用的函数,通过
LuaState.LuaRegisterFunction()
函数将这些函数注册到Lua虚拟机中。 - 在C#中调用Lua函数时,首先需要将参数依次压入虚拟栈中。
- 然后,通过
LuaState.LuaGetFunction()
函数获取需要调用的Lua函数的指针,并通过LuaState.LuaCall()
函数调用Lua函数。 - 在Lua函数执行完成后,将返回值依次压入虚拟栈中。在C#中获取返回值时,可以通过
LuaState.LuaToNumber()
、LuaState.LuaToString()
等函数将虚拟栈中的值转换为C#中的类型。
- 实质:C#把请求或数据放在栈顶,然后Lua从栈顶取出该数据,在Lua中做出相应处理(查询,改变),然后把处理结果放回栈顶,最后C#再从栈顶取出Lua处理完的数据,完成交互。
- C#->Bridge->DLL->Lua OR C#->DLL(C)->Lua
Lua调用C#
Wrap方式(静态生成代码)
- 过程:首先C#源文件生成对应的Wrap(中间文件/适配文件)文件,Wrap文件把字段方法,注册到Lua虚拟机中(解释器Luajit),然后Lua通过Wrap就可以调C#了
- 详细过程:
- 首先,需要在C#中定义一个类,该类包含需要在Lua中访问的方法和属性。
- 然后,定义一个C#的接口,该接口包含一个方法
PushToLua()
,用于将C#对象转换为Lua的table。在该方法中,需要使用Lua API将C#对象的属性和方法转换为Lua table中的成员。 - 在C#中创建一个子类,该子类继承自C#类和C#接口,并实现接口中的
PushToLua()
方法。在该方法中,可以使用Lua API将C#对象的属性和方法转换为Lua table中的成员。 - 在Lua中,通过调用C#接口中的
PushToLua()
方法将C#对象转换为Lua的table,并将该table注册为Lua全局变量或者局部变量。这样,在Lua中就可以通过访问该table来访问C#对象的属性和方法了。
- 实质:Lua把请求或数据放在栈顶,然后C#从栈顶取出数据,在C#中做出相应处理(查询,改变),然后把处理结果放回栈顶,最后Lua再从栈顶取出C#处理完的数据,完成交互。
- Lua->Wrap->C#
- 可能产生的问题:
- 生成有宏定义的方法,如Editor会有问题
- 生成的代码,尤其使用il2cpp,会增大编译后的代码大小相应地增加包体大小,
- Wrap模式的优点是可以使得Lua程序可以方便地访问C#中的对象和方法,同时也可以保证程序的类型安全性。缺点是需要在C#中实现接口和子类,并且需要进行类型转换,使得代码的可读性和可维护性都会受到影响。
反射方式
- 当索引系统中的API、DLL库或者第三方库时,如果无法将代码的具体实现进行代码生成,可采用此方式实现交互,执行效率低,所以一般使用Wrap方式调用C#。
- 可能产生的问题:
- 拆装箱开销
- stripping引用的反射失效
- 泛型方法,触发JIT,引起IOS异常
- 失去静态检查好处
- 详细过程:
- 首先,Lua需要通过
clr
包加载C#程序集,然后通过反射机制获取需要调用的C#方法。 - 在Lua中调用C#方法时,需要将方法的参数依次压入虚拟栈中。
- 然后,调用
LuaDLL.lua_pcall()
函数来调用C#方法。在调用时,需要将C#方法的指针压入虚拟栈中。 - C#方法执行完成后,将返回值依次压入虚拟栈中。在Lua中获取返回值时,可以通过
LuaDLL.lua_tonumber()
、LuaDLL.lua_tostring()
等函数将虚拟栈中的值转换为Lua中的类型。
- 首先,Lua需要通过