找回密码
 立即注册
查看: 242|回复: 0

unity tolua异常处理

[复制链接]
发表于 2021-8-11 17:52 | 显示全部楼层 |阅读模式
C#、C、Lua分别有不同的异常处理机制,在跨语言函数调用的时候,必须要正确的处理异常,否则会导致堆栈错误、内存泄露、程序崩溃等问题。tolua对此做了非常全面的安全处理,值得我们去学习。如果我们要自己去做一些C层面的扩展,也必须要对这些底层原理熟记于心,才能避免各种诡异问题。
本文作者游蓝海,未经许可禁止转载。

文章目录

    C#调用Lua函数

        LuaException

    Lua调用C#方法

        注册C#函数调用C#函数




C#调用Lua函数

C#调用Lua函数的时候,必须使用lua_pcall接口进行调用。因为lua_pcall会拦截C语言层面的异常,确保异常不会破坏掉C#解释器的堆栈。
lua_pcall API说明:
  1. int lua_pcall (lua_State *L,int nargs,int nresults,int error);
复制代码
参数名称说明
LLua解释器指针
nargs参数数量
nresults返回值数量
error异常处理函数句柄。也就是处理函数在Lua栈上的索引
如果函数调用成功,返回0,并且在Lua栈push上nresults个结果(如果实际结果不足,会push nil);否则,返回相应的错误码,并且在Lua栈push上错误原因的字符串。不管是否执行成功,lua_pcall都会把push的函数和nargs个参数从栈顶清除。
如果指定了错误处理函数,在函数调用失败的时候,Lua会调用该错误处理函数来处理错误原因。通常情况下,我们可以传入debug.traceback函数,这样可以得到函数调用堆栈。
ToLua在BeginPCall的时候,会将错误处理函数traceback和要调用的函数压栈。在PCall的时候,如果函数调用出错,则向C#层面抛出异常LuaException。
C# LuaState类部分源码:
  1. // 将错误处理函数和要调用的函数压栈publicintBeginPCall(int reference){return LuaDLL.tolua_beginpcall(L, reference);}// 执行函数调用publicvoidPCall(int args,int oldTop){if(LuaDLL.lua_pcall(L, args, LuaDLL.LUA_MULTRET, oldTop)!=0){string error =LuaToString(-1);thrownewLuaException(error, LuaException.GetLastError());}}// 清理堆栈publicvoidEndPCall(int oldTop){
  2.     LuaDLL.lua_settop(L, oldTop -1);}
复制代码
LuaException

LuaException同时记录了Lua层的异常信息和C#层的信息,如果要做异常上报和分析,需要了解LuaException的结构。
  1. publicclassLuaException:Exception{publicLuaException(string msg,Exception e =null);//返回异常产生时的C#堆栈publicoverridestring StackTrace;//返回最近一次的Lua异常publicstaticExceptionGetLastError();}
复制代码
构造函数参数说明
msg通常是错误原因加上Lua堆栈。如果是Lua端出了异常,msg为Lua端的错误原因和Lua堆栈。如果是C#端出了异常,msg是C#异常名称和Lua堆栈,如果要看C#端的堆栈,需要修改C#的toluaL_exception,从异常对象中获取完整的异常信息。
e上一个异常对象。可以是C#异常,也可以是Lua抛出的异常。因为函数可能会递归调用,底层的异常需要一层层的向外抛。

Lua调用C#方法

C#方法在注册到Lua的时候,有两个步骤:
    先在C#层生成一个静态的C# Wrap函数,并传递给C语言层;然后在C语言层面经过一层函数包装(tolua_closure),生成一个新的闭包函数,然后在注册到Lua表中。
当Lua层调用C#函数的时候,也是两个步骤:
    先调用C函数tolua_closure(之前注册时生成的的闭包函数);tolua_closure内部会调用真正的C# Wrap函数。
如果调用C#的过程中出现了异常,则tolua_closure会将这个C#的异常信息,用lua_error抛给Lua。
注册C#函数

C#函数在push给Lua的时候,会调用接口tolua_pushcfunction,将执行结果标记和C#函数作为tolua_closure的闭包参数(upvalue)。
tolua_pushcfunction源码:
  1. //hack for luac, 避免luac error破坏包裹c#函数的异常块(luajit采用的是类似c++异常)
  2. LUA_API inttolua_pushcfunction(lua_State *L, lua_CFunction fn){// 第1个闭包参数:结果标记。为true表示函数执行过程中出了异常lua_pushboolean(L,0);// 第2个闭包参数:要包装的C#函数lua_pushcfunction(L, fn);// 生成闭包函数lua_pushcclosure(L, tolua_closure,2);return0;}
复制代码
lua_pushcclosure API说明:
  1. void lua_pushcclosure (lua_State *L, lua_CFunction fn,int n);
复制代码
参数名称说明
LLua解释器指针
fnC函数指针
nn个闭包变量(upvalue)
生成一个新的闭包函数放到栈顶。可以指定n个闭包参数,参数需要先push到栈顶(第一个参数要第一个push)。在闭包函数中,通过函数lua_upvalueindex(index)来获取闭包参数的索引,然后调用其他lua api来获取或设置值。
调用C#函数

Lua中调用C#函数的时候,实际上调用的是闭包函数tolua_closure。tolua_closure取到tolua_pushcfunction传入的闭包参数,也就是真正的C#函数指针,然后调用C#函数。
tolua_closure源码:
  1. inttolua_closure(lua_State *L){// 拿到闭包变量2,也就是真正的C#函数
  2.     lua_CFunction fn =(lua_CFunction)lua_tocfunction(L,lua_upvalueindex(2));// 调用C#函数int r =fn(L);// 如果C#函数中出现异常if(lua_toboolean(L,lua_upvalueindex(1))){// 恢复异常标记,确保不会影响下一次函数调用lua_pushboolean(L,0);lua_replace(L,lua_upvalueindex(1));// 将异常抛给Lua解释器returnlua_error(L);}return r;}
复制代码
C# Wrap函数都必须处理所有的C#异常,然后将异常信息通过toluaL_exception传递给C层:
  1. publicstaticinttoluaL_exception(IntPtr L,Exception e){            
  2.     LuaException.luaStack =newLuaException(e.Message, e,2);returntolua_error(L, e.ToString());}
复制代码
tolua_error向当前正在执行的C闭包函数写入错误标记,当前函数执行完毕后,由tolua_closure来检查执行结果:
  1. inttolua_error(lua_State *L,constchar*msg){// 将错误标记(true)赋值给第一个闭包变量lua_pushboolean(L,1);lua_replace(L,lua_upvalueindex(1));// 返回错误原因lua_pushstring(L, msg);return1;}
复制代码
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 07:46 , Processed in 0.064399 second(s), 22 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表