- 关于lua_kernel_base::lua_kernel_base参考“初始化lua环境”。
- lua_pcall时,会把db_traceback设为错误处理函数,功能是根据运行现场,补上更多出错时信息,让开发者能更容易定位错误。
- 除了第一个package.lua,它实现了rose.require方法。其它lua文件都是通过rose.require进行加载。rose.require不仅调用lua_load加载lua文件,还会调用lua_call执行该文件顶层函数
一、错误处理
1.1 执行luaopen_debug(),向lua运行环境添加db_traceback方法
lua_kernel_base()会执行“luaL_requiref(L, lib->name, lib->func, 1)”,当中有个lib是“debug”,对应的func是luaopen_debug(),后者有条记录:{"traceback", db_traceback}。
经过这操作后,_ENV添加了一条记录:{_ENV.debug, [dblib]},[dblib]是一个表,当中有条记录:{"traceback", db_traceback}。
1.2 _ENV.debug表包含数种操作,只让保留“traceback”、“getinfo”
认为_ENV.debug表的一些方法有替代方案,避免将来会被调用,lua_kernel_base()会删除它们,但会保留“traceback”、“getinfo”
1.3 push_error_handler。向注册表添加一条记录:{key=err, debug.traceback}
lua_kernel_base::lua_kernel_base()会调用push_error_handler。
<librose>/scripts/lua_common.cpp ------ static const char executeKey[] = "err"; void push_error_handler(lua_State *L) { luaW_getglobal(L, "debug", "traceback"); lua_setfield(L, LUA_REGISTRYINDEX, executeKey); }
push_error_handler向注册表添加一条记录:{key=err, _ENV.debug.traceback}。
1.4 lua_pcall时,将debug.traceback设为错误处理函数
运行时某个时刻,C要调用lua函数了,在将lua函数、参数入栈后,会调用luaW_pcall_internal。
int luaW_pcall_internal(lua_State *L, int nArgs, int nRets) { // Load the error handler before the function and its arguments. lua_getfield(L, LUA_REGISTRYINDEX, executeKey); lua_insert(L, -2 - nArgs); int error_handler_index = lua_gettop(L) - nArgs - 1; // Call the function. // -1 - nArgs放在是待调用lua函数 int errcode = lua_pcall(L, nArgs, nRets, -2 - nArgs); // lua_pcall会自动弹出被调函数及参数,但不会弹错误处理函数 // 弹出错误处理函数 lua_remove(L, error_handler_index); return errcode; }
在调用lua_pcall时,第二个参数表示传递的参数数量,第三个参数是期望的返回值数量,第四个参数代表错误处理函数。
如果lua_pcall在运行过程中出现错误,它会返回一个错误码,并在栈中压入一条字符串类型的错误信息(但是仍会弹出函数及参数)。不过,如果有错误处理函数,在压入错误信息前,lua_pcall会先调用错误处理函数。我们可以通过lua_pcall最后一个参数指定这个错误处理函数,零表示没有错误处理函数,即最终的错误信息就是原来的消息;若传入非零参数,那么参数应该是该错误处理函数在栈中索引。在这种情况下,错误处理函数应该被压入栈,且位于待调用函数之下。
上面代码在调用lua_pcall时,stkidx = -1 - nArgs放的是待调用lua函数,错误处理函数位于它之下,即“-2 - nArgs”。
lua_pcall会自动弹出被调函数及参数,但不会弹出错误处理函数。error_handler_index是用正值表示的、错误处理函数在栈中的索引。
1.5 出错示例:error("Tried to access an empty package", 2)
为看到出错实例,让在lua脚本增加一条语句:error("Tried to access an empty package", 2);
error第一个参数是字符串表示的错误描述,第二个参数是要返回给lua_pcall的错误码。这条error被执行后,lua_pcall的返回值,即luaW_pcall_internal中的变量errcode将是2。
1.6 lua_pcall调用debug.traceback,即db_traceback
“如果有错误处理函数,在压入错误信息前,lua_pcall会先调用错误处理函数”,由于设置了db_traceback是错误处理函数,lua_pcall执行到“error”触发错误后,会先调用db_traceback,db_traceback则调用luaL_traceback。luaL_traceback根据运行现场,补上更多出错时信息,让开发者能更容易定位错误。
Tried to access an empty package stack traceback: [C]: in function '.error' lua/package.lua:9: in main chunk
以上是luaL_traceback根据运行现场补充后的信息,当中指出出错的lua文件、行号。
1.7 lua_tostring(L, -1)得到错误信息
{ int errcode = luaW_pcall_internal(L, nArgs, nRets); if (errcode != LUA_OK) { char const * msg = lua_tostring(L, -1); // msg就就是经过db_traceback补充后的出错信息 // 处理msg。一旦调用下面的lua_pop,msg这指针就无效了。 lua_pop(L, 1); return false; } return true; }
二、加载*.lua文件
void lua_kernel_base::load_core() { lua_State* L = mState; luaW_getglobal(L, "rose", "require"); lua_pushstring(L, "lua/core.lua"); if (!protected_call(1, 1)) { // 加载lua/core.lua失败 } }
以上是一个典型的加载lua文件示例,要加载的文件是lua/core.lua。核心是以“lua/core.lua”为参数,调用lua脚本函数rose.require。须要说下的是,rose.require不仅是调用lua_load加载lua文件,还会调用lua_call执行该文件顶层函数,见之下的lua_kernel_base::intf_dofile。
rose.require是什么时候加到lua运行环境?——lua_kernel_base()会加载lua/package.lua,package.lua实现了rose.require。
2.1 (lua函数)rose.require
<apps-res>/data/core/lua/package.lua ------ function rose.require(pkg_name) -- First, check if the package is already loaded local loaded_name = resolve_package(pkg_name) -- don't user cache. for applet, it's *.lua require load every time. -- if loaded_name and rose.package[loaded_name] then -- return rose.package[loaded_name] -- end if not loaded_name then rose.log("err", "Failed to load required package: " .. pkg_name, true) return nil end
resolve_package是个lua函数,如果pkg_name存在,返回的loaded_name就是pkg_name。
-- Next, if it's a single file, load the package with dofile if rose.have_file(loaded_name, true) then local pkg = rose.dofile(loaded_name) rose.package[loaded_name] = pkg or empty_pkg
rose.have_file判断该文件是否存在,rose.dofile指向一个C函数,依次执行lua_load、lua_call。
return pkg else -- If it's a directory, load all the files therein
loaded_name可能是个目录
local files = rose.read_file(loaded_name) local pkg = {} for i = files.ndirs + 1, #files do if files[i]:sub(-4) == ".lua" then local subpkg_name = files[i]:sub(1, -5) pkg[subpkg_name] = rose.require(loaded_name .. '/' .. files[i]) end end rose.package[loaded_name] = pkg return pkg end end
rose.require只一个参数:pkg_name,指示要加载的lua文件。
2.2 luaW_getglobal
判断path[0].path[..].path[path.size()-1]是否是一个lua函数,是的话,把这函数压栈。执行完luaW_getglobal后,栈会增加一个单元,就是这个lua脚本函数。
bool luaW_getglobal(lua_State *L, const std::vector<std::string>& path) { lua_pushglobaltable(L);
rose一定存在于_ENV中,首先把_ENV压栈。
for (const std::string& s : path) { // 处理path中的#i单元 if (!lua_istable(L, -1)) goto discard; lua_pushlstring(L, s.c_str(), s.size()); lua_rawget(L, -2); // lua_remove(L, -2);
针对:luaW_getglobal(L, "rose", "require");
第一轮for循环。以key=rose搜索_ENV,找到_ENV.rose这个表,入栈。并从栈出弹出表示_ENV的单元。
第二轮for循环。以key=require搜索_ENV.rose,找到rose.require这个lua函数,入栈。并从栈出弹出表示_ENV.rose的单元。
} if (lua_isnil(L, -1)) { VALIDATE(false , null_str); discard: lua_pop(L, 1); return false; } return true; }
2.3 lua_kernel_base::intf_dofile
在lua脚本,rose.require会调用rose.dofile,它对应的是C函数:lua_kernel_base::intf_dofile。
int lua_kernel_base::intf_dofile(lua_State* L) { luaL_checkstring(L, 1); lua_rotate(L, 1, -1); if (lua_fileops::load_file(L) != 1) return 0; //^ should end with the file contents loaded on the stack. // actually it will call lua_error otherwise, the return 0 is redundant. lua_rotate(L, 1, 1); // Using a non-protected call here appears to fix an issue in plugins. // The protected call isn't technically necessary anyway, because this function is called from Lua code, // which should already be in a protected environment. lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); return lua_gettop(L); }
intf_dofile依次执行两个任务,一是lua_load加载lua文件,二是lua_call执行该lua文件顶层函数。