xLua学习笔记(二) C#调用Lua代码


获取全局变量

只需要调用LuaEnv对象Global属性的Get方法即可

LuaTable Global;
  • 描述:
    代表lua全局环境的LuaTable
T Get<T>(string key); 
  • 描述:
    获取在key下,类型为T的value,如果不存在或者类型不匹配,返回null;

例如有如下Lua代码

number = 1 string = "hello world" boolean = true 

在C#中尝试用上述方法输出Lua中的全局变量number,string和boolean

Debug.Log("number = " + luaenv.Global.Get<int>("number")); Debug.Log("string = " + luaenv.Global.Get<string>("string")); Debug.Log("boolean = " + luaenv.Global.Get<bool>("boolean")); 

得到结果

 
 

如果需要获取Lua中Table的数据,则需要将Table映射为C#中相对应的数据结构,可选的方式有:classinterfaceDictionaryListLuaTable

假设有如下的Table

table =
{
    f1 = 1,
    f2 = 2
}

如果要使用class做映射,只需要在C#中定义一个相对应的类即可,注意变量名称要和Table中的名称相同,xLua会依次在table中寻找是否有与class中变量名相同的key值,如果有则将其value值复制到对应的变量上,如果在Table的Key值中找不到与定义的class的变量,则变量会被赋于对应类型的初值

class Table { public int f1; public int f2; } 

获取变量的方法还是和之前一样

Table table = luaenv.Global.Get<Table>("table"); Debug.Log("table.f1 = " + table.f1); Debug.Log("table.f2 = " + table.f2); 

得到结果

 
 

同样地,还可以使用DictionaryLuaTable(速度比较慢)来映射Table,代码如下

Dictionary<string, int> dict = luaenv.Global.Get<Dictionary<string, int>>("table"); Debug.Log("dict[f1] = " + dict["f1"]); Debug.Log("dict[f2] = " + dict["f2"]); LuaTable luaTable = luaenv.Global.Get<LuaTable>("table"); Debug.Log("luaTable.Get<int>(\"f1\") = " + luaTable.Get<int>("f1")); Debug.Log("luaTable.Get<int>(\"f2\") = " + luaTable.Get<int>("f2")); 

得到结果

 
 
 
 

对于下面这种Table,可以使用List来做映射

table =
{
    1,
    2
}
List<int> list = luaenv.Global.Get<List<int>>("table"); Debug.Log("list[0] = " + list[0]); Debug.Log("list[1] = " + list[1]); 

得到结果

 
 

如果Table中存在函数,例如

table =
{
    f1 = 1, f2 = 2, add = function(self, num1, num2) return num1 + num2 end } 

可以使用interface来映射,对于上述的Table,可以声明如下ITable接口

使用这种方法读取Table时需要生成代码,所以必须要给接口加上一个Attribute:[CSharpCallLua]

[CSharpCallLua] interface ITable { int f1 { get; set; } int f2 { get; set; } int add(int num1, int num2); } 
ITable iTable = luaenv.Global.Get<ITable>("table"); Debug.Log("table.f1 = " + iTable.f1); Debug.Log("table.f2 = " + iTable.f2); Debug.Log("table.add(1, 2) = " + iTable.add(1, 2)); 

得到结果

 
 

获取全局函数

一般来说,全局函数的映射方式有两种,一种是使用delegate(要生成代码,性能好),另一种是使用LuaFunction(不用生成代码,性能较差)。

假设有下列的全局函数

function action0() print ("action0() called!") end 

因为使用delegate的方式映射全局函数需要生成代码,所以必须要添加一个Attribute:[CSharpCallLua]

[CSharpCallLua] private delegate void Action0(); 

获取的方式大同小异

Action0 action0 = luaenv.Global.Get<Action0>("action0"); action0(); 

得到结果

 
 

当全局函数有一个或多个返回值的时候,按照在函数中的返回顺序,从左到右依次对应到delegate返回值ref/out参数

假设有下列的全局函数

function action2(param1, param2) print("param1:", param1, " param2:", param2) return 1, {f1 = 2} end 

那么下面六种delegate的映射方式是等效的,

[CSharpCallLua] private delegate int Action2(int param1, int param2, out Table table); [CSharpCallLua] private delegate int Action2_(int param1, int param2, ref Table table); [CSharpCallLua] private delegate void Action2__(int param1, int param2, out int val ,out Table table); [CSharpCallLua] private delegate void Action2___(int param1, int param2, ref int val, ref Table table); [CSharpCallLua] private delegate void Action2____(int param1, int param2, ref int val, out Table table); [CSharpCallLua] private delegate void Action2_____(int param1, int param2, out int val, ref Table table); 

delegate的返回值也可以是delegate,假设有下列的全局函数

function action0() print ("action0() called!") end function get_action0() print("get_action0() called!") return action0 end 

先定义Action0,然后将GetAction0的返回值设为Action0即可

[CSharpCallLua] private delegate void Action0(); [CSharpCallLua] private delegate Action0 GetAction0(); 
GetAction0 getAction0 = luaenv.Global.Get<GetAction0>("get_action0"); (getAction0())(); 

运行得到结果

 
 

使用LuaFunction来映射全局函数的方法与使用delegate时基本相同,映射后如果要在C#中使用函数,只需要调用LuaFunction对象的Call方法即可,Call方法有下面两种重载形式

object[] Call(params object[] args) 
  • 描述:
    以可变参数调用Lua函数,并返回该调用的返回值。
object[] Call(object[] args, Type[] returnTypes) 
  • 描述:
    调用Lua函数,并指明返回参数的类型,系统会自动按指定类型进行转换。

需要注意的是,当函数有返回值的时候,最好指明返回参数的类型,如果没有指明类型,在将object对象转换具体类型的时候可能会InvalidCastException异常

假设有下面的全局函数

function action2(param1, param2) print("param1:", param1, " param2:", param2) return 1, {f1 = 2} end 

下面的C#代码使用LuaFunction调用该全局函数

LuaFunction luaFunction = luaenv.Global.Get<LuaFunction>("action2"); object[] vals = luaFunction.Call(new object[]{1, 2}, new Type[] { typeof(int), typeof(Table) }); Debug.Log("vals[0] = " + (int)vals[0]); Debug.Log("vals[1] = " + ((Table)vals[1]).f1); 

得到结果

 
 

LuaFunction访问Lua函数的过程涉及到了多次的装箱与拆箱操作,虽然不用生成代码,但是性能的消耗是比较大的

由于访问Lua全局数据,特别是table以及function,代价比较大,推荐使用单独的模块负责管理其加载,而不是每次用之前去加载

资源释放

下面这段这段C#代码首先将Lua函数test映射到了委托action上,使用完成后调用了LuaEnv的Dispose方法回收了LuaEnv对象

public class SpecialCase : MonoBehaviour { [CSharpCallLua] public delegate void Action(); private LuaEnv luaenv; private Action action; void Start () { luaenv = new LuaEnv(); luaenv.DoString ( @" function test() print('test') end " ); action = luaenv.Global.Get<Action>("test"); luaenv.Dispose(); } } 

运行上面这段代码,发现抛出了如下的异常

 
 

查阅FAQ

调用LuaEnv.Dispose时,报“try to dispose a LuaEnv with C# callback!”错是什么原因?

这是由于C#还存在指向lua虚拟机里头某个函数的delegate,为了防止业务在虚拟机释放后调用这些无效(因为其引用的lua函数所在虚拟机都释放了)delegate导致的异常甚至崩溃,做了这个检查

怎么解决?释放这些delegate即可,所谓释放,在C#中,就是没有引用

你是在C#通过LuaTable.Get获取并保存到对象成员,赋值该成员为null

你是在lua那把lua函数注册到一些事件事件回调,反注册这些回调

如果你是通过xlua.hotfix(class, method, func)注入到C#,则通过xlua.hotfix(class, method, nil)删除

要注意以上操作在Dispose之前完成

因此,只需要在LuaEnv释放之前,将Lua函数映射的委托变量action设置为null即可

action = luaenv.Global.Get<Action>("test"); action = null; luaenv.Dispose();


作者:BlauHimmel
链接:https://www.jianshu.com/p/8dde7be41d96
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM