一、异常处理
lua 使用了 c 语言的 setjmp 机制,setjmp 营造了一个类似异常处理的机制。因此大多数 api 函数都可以抛出异常(即调用函数 longjmp)而不是直接返回。
这里关注的视角是 c/c++ 提供一个运行环境,lua 作为一个脚本在其中运行,所以最为重要的是脚本的异常不影响宿主的正常运行。 要做到这一环节最重要的是考虑如何处理好其中的异常流程。归纳起来有四种交互方式:
- c++ 调用 c++ 代码,这只是纯粹的 c++ 编码,不在 c++ 和 lua 交互的涉猎范围,需要的话可以查阅 c++ 异常捕获的相关文章。
- c++ 调用 lua 代码,这一过程发生在宿主 c++ 驱动 lua 脚本代码,lua 代码中可能会发生异常,这一异常需要让 c++ 能够感知到并处理,这一过程会用到
lua_pcall
(下面会详细分享) 。 - lua 调用 lua 代码,这一过程发生在脚本代码调用脚本代码,则使用
pcall
函数(详细使用看前面分享的 《lua 编译执行和错误处理》 文章)。或是发生在脚本代码调用自己封装的 lua 库代码(详细可以看前面分享的 《lua 模块与包》)。这两种调用方式都有可能发生异常,两者都能在返回值接收到异常信息,所以这里更多是业务层面需要的如何处理,即按需求是中断流程还是继续流程。 - lua 调用 c++ 代码,这一过程发生在 lua 调用宿主的能力,或是 lua 调用 c 标准库的能力,这里的标准库可能会有异常的发生,c/c++ 代码异常则需要自行捕获(就和 “第一点” 一样),或是自身流程需要抛出异常,这过程需要让 lua 脚本感知,则需要通过
lua_error
(下面会详细分享)进行抛回给 lua 脚本,而不是直接throw
之类的抛出异常。
第一小点不在我们这系列文章的涉猎范围,也就不详细分享了。
第三小点在前面的文章已经详细分享,需要的话可以翻阅之前的文章,也就不赘述了。
我们接下来基于第二点和第四点进行详细的分享。
二、c++ 处理 lua 异常
1、c++ 如何运行 lua 代码
可以通过 lua_call
和 lua_pcall
两个函数调用 lua 代码。
int lua_call(lua_state *l, int nargs, int nresults);
int lua_pcall(lua_state *l, int nargs, int nresults, int errfunc);
两者均用于在 c/c++ 代码中调用 lua 函数,不同点在于:
lua_call
会将代码中的异常直接抛出,导致程序中断。lua_pcall
提供一个保护模式运行 lua 代码,即使发生异常,也会被捕获,并可以通过第四个参数的错误处理函数处理错误,程序不会因此而中断。
参数:
- l:lua state 的指针。
- nargs:传递给被调用函数的参数个数。
- nresults:期望的返回值个数。
- errfunc:错误处理函数的索引,用于处理发生的错误。如果为 0,则错误信息会被压入栈顶。
返回值:
- 函数调用成功,返回 0,并将返回值压入栈中。
- 如果函数调用发生错误,返回一个非零值,并将错误信息压入栈中。
2、lua_pcall 的错误处理函数
错误处理函数的格式为一个返回 int 类型,入参为 lua_state 类型的函数
int functionname(lua_state *l);
传递给 “错误处理函数” 的 lua_state
中的栈是一个新的栈,和调用 lua_pcall
的函数栈并非同一个。栈中有一个元素,即 lua 代码中抛出的异常。
可以在 “错误处理函数” 内部通过这个元素得知 lua 代码中的异常信息,然后可以对错误信息进行加工后再压入栈,最后返回给 c/c++ 宿主程序,c/c++ 宿主程序通过 lua_state 的栈中栈顶元素即为错误信息。
lua 的异常信息可以是 lua 自行抛出的,例如对一个 nil
变量调用了一个方法,或是程序员因为业务流程的需要使用 error
函数(具体使用可以查看前面分享的 《lua 编译执行和错误处理》 文章)抛出异常。所以这一异常并不一定是字符串,可以是 table 类型等 lua 的数据类型。
3、举个例子
举一个例子捋顺这一小节的使用流程,下面例子的流程为:
- 通过 c++ 调用 lua 的脚本代码
- 在 lua 的脚本代码中抛出异常
- 然后 c++ 的错误处理函数中增加 lua 抛出异常的堆栈信息,压入栈中返回给调用点
- 最后调用点,即第一点的调用处获取 lua 脚本的运行结果,如果失败则打印错误信息
先看 c++ 调用的代码,其中 safecalllua
是对 lua_pcall
进行了封装,可以看最后面的代码段
lua_state *l = lual_newstate();
// 加载函数库,否则加载的 lua 无法使用内置库
lual_openlibs(l);
auto luafilepath = project_path + "/3、c++调用lua的错误处理和内存分配/c++调用lua异常处理/c运行的lua文件.lua";
// 加载 lua 脚本
auto loadluaresult = lual_loadfile(l, luafilepath.c_str());
if (loadluaresult) {
printf("加载 lua 文件失败. file: %s\n", lua_tostring(l, -1));
return;
}
// 压入参数,传递给 lua 脚本
lua_pushstring(l,
发表评论