LuaC API知识点汇总
一.概述
首先,关于学习LuaC API的目的,本人是为了为深度学习Unity的ToLua框架做知识储备,ToLua里很多类中都调用的是LuaC的API
本文意在对LuaC API的重要概念进行精简和总结,信息出自Programming in Lua Part IV,如下图所示
二 栈的概念
尝试在LUA和C之间交换值时,面临两个问题:动态和静态类型系统之间的不匹配以及自动和手动内存管理之间的不匹配。虚拟栈的作用正是为了解决这两个问题。
LUA到C以及从C到LUA的所有数据交换都是通过此栈进行的,进行过入栈出栈的操作后才能访问到Lua或C的值。
C的API不是以严格的后进先出操作栈;它在任何任意位置插入和删除元素。
三.栈内简单操作
3.1 栈内索引
要引用栈中的元素,API 使用索引。栈中的第一个元素(即最先被压入的元素)索引为 1,下一个元素索引为 2,依此类推。我们也可以以栈顶为参照使用负索引访问元素。在这种情况下,-1 指的是栈顶的元素(即最后被压入的元素),-2 指的是其前一个元素,依此类推。例如,调用 lua_tostring(L, -1) 会将栈顶的值作为字符串返回。
3.2 元素入栈
API 为可在 C 语言中表示的每种 Lua 类型都提供了一个 push 函数
void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, double n);
void lua_pushlstring (lua_State *L, const char *s, size_t length);
void lua_pushstring (lua_State *L, const char *s);
每当您将一个元素压入栈中时,您有责任确保栈有足够的空间容纳它。请记住,您现在是一名 C 语言程序员;Lua 不会宠坏您。当 Lua 启动以及每次 Lua 调用 C 时,栈至少有 20 个空闲槽位(此常量在 lua.h 中定义为 LUA_MINSTACK)。对于大多数常见用途来说,这已经绰绰有余,所以通常我们甚至都不会考虑这个问题。然而,某些任务可能需要更多的栈空间(例如,调用具有可变数量参数的函数)。在这种情况下,您可能需要调用
int lua_checkstack (lua_State *L, int sz);
这个函数用于检查栈是否有足够的空间来满足您的需求。
3.3 元素类型查询
为了检查一个元素是否具有特定类型,该 API 提供了一组以 lua_is 开头的函数,其中 * 可以是任何 Lua 类型。因此,有 lua_isnumber、lua_isstring、lua_istable 等等。所有这些函数都具有相同的原型:
int lua_is... (lua_State *L, int index);
lua_isnumber 和 lua_isstring 函数并非检查值是否具有特定类型,而是检查值是否可以转换为该类型。例如,任何数字都满足 lua_isstring 的条件。
还有一个函数可返回栈中指定索引位置元素的类型:
int lua_type (lua_State *L, int index);
类型的定义在lua.h
中:LUA_TNIL
, LUA_TBOOLEAN
, LUA_TNUMBER
, LUA_TSTRING
, LUA_TTABLE
, LUA_TFUNCTION
, LUA_TUSERDATA
, ,LUA_TTHREAD
此函数主要用于与 switch 语句结合使用。当需要检查字符串和数字而不进行强制转换时,它也很有用。
3.4 元素取值
要从栈中获取值,可以使用 lua_to* 系列函数,这类函数不会移除访问的元素:
int lua_toboolean (lua_State *L, int index);double lua_tonumber (lua_State *L, int index);const char *lua_tostring (lua_State *L, int index);size_t lua_strlen (lua_State *L, int index);
即使给定的元素类型不正确,也可以调用这些函数。在这种情况下,lua_toboolean、lua_tonumber 和 lua_strlen 函数返回零,其他函数返回 NULL。零本身没有用处,但 ANSI C 没有提供我们可以用来表示错误的无效数值。然而,对于其他函数,我们通常不需要使用相应的 lua_is* 函数:我们只需调用 lua_to* 函数,然后测试结果是否不为 NULL 即可。
3.5 其他栈的操作
API还提供了以下通用栈操作功能
int lua_gettop (lua_State *L);
返回栈顶部元素的索引。该结果等于栈中元素的数量;0 表示空栈。
void lua_settop (lua_State *L, int index);
接受任意索引或 0,并将栈顶设置为该索引。如果新的栈顶大于旧的栈顶,则新元素将填充 nil。如果索引为 0,则所有栈元素都将被移除。
void lua_pushvalue (lua_State *L, int index);
将指定索引处的元素副本压入栈中。
void lua_remove (lua_State *L, int index);
删除给定有效索引处的元素,向下移动此索引以上的元素以填补空白。
void lua_insert (lua_State *L, int index);
将顶部元素移动到给定的有效索引中,将此索引上方的元素上移到空白处。
void lua_replace (lua_State *L, int index);
将顶部元素移动到给定的有效索引中,而不移动任何元素(因此替换该给定索引处的值),然后弹出顶部元素。
下面给出一个例子,演示API的应用
extern "C"
{
#include <Lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
#include <iostream>static void stackDump(lua_State* L) {int i;int top = lua_gettop(L);for (i = 1; i <= top; i++) { /* repeat for each level */int t = lua_type(L, i);switch (t) {case LUA_TSTRING: /* strings */printf("`%s'", lua_tostring(L, i));break;case LUA_TBOOLEAN: /* booleans */printf(lua_toboolean(L, i) ? "true" : "false");break;case LUA_TNUMBER: /* numbers */printf("%g", lua_tonumber(L, i));break;default: /* other values */printf("%s", lua_typename(L, t));break;}printf(" "); /* put a separator */}printf("\n"); /* end the listing */
}int main()
{lua_State* L = luaL_newstate();lua_pushboolean(L, 1); lua_pushnumber(L, 10);lua_pushnil(L); lua_pushstring(L, "hello");stackDump(L);/* true 10 nil `hello' */lua_pushvalue(L, -4); stackDump(L);/* true 10 nil `hello' true */lua_replace(L, 3); stackDump(L);/* true 10 true `hello' */lua_settop(L, 6); stackDump(L);/* true 10 true `hello' nil nil */lua_remove(L, -3); stackDump(L);/* true 10 true nil nil */lua_settop(L, -5); stackDump(L);/* true */lua_close(L);return 0;
}
四. 栈内table操作
先来看一个例子:先在栈中创建一个空table,然后赋值table["level"]=10,再读取table["level"]并打印的操作
extern "C"
{
#include <Lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
#include <iostream>
using namespace std;int main()
{lua_State* L = luaL_newstate();lua_newtable(L);lua_pushstring(L, "level");lua_pushnumber(L, 10);lua_settable(L, 1);//完成栈中table["level"] = 10的赋值cout << "lua_settable后栈长:" << lua_gettop(L) << endl;lua_pushstring(L, "level");lua_gettable(L, 1);//从栈中获取table["level"]的值,并将栈顶元素替换cout << "lua_gettable后栈长:" << lua_gettop(L) << endl;int num = lua_tonumber(L, -1);cout << num << endl;lua_pop(L, 1);cout << "lua_pop后栈长:" << lua_gettop(L) << endl;cout << lua_typename(L, lua_type(L, 1)) << endl;lua_close(L);return 0;
}
API分析:
void lua_newtable (lua_State *L);
创建一个新的空表并将其推送到栈上
void lua_settable (lua_State *L, int index);
等价于t[k]=v,其中t是给定索引处的值,v是栈顶部的值,k是栈顶部正下方的值。
此函数从栈中弹出键和值。
int lua_gettable (lua_State *L, int index);
将栈顶元素弹出,并将t[k]的值推入栈顶,其中t是给定索引处的值,k是栈顶部的值。
void lua_pop (lua_State *L, int n);
从栈中弹出n个元素。
五. C调用Lua函数
先来看一个demo
extern "C"
{
#include <Lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
#include <iostream>
using namespace std;int main()
{lua_State* L = luaL_newstate();luaL_openlibs(L);const char* lua_script = "function AddOne(num) return num + 1 end";int status = luaL_dostring(L, lua_script);lua_getglobal(L, "AddOne");lua_pushnumber(L, 5);cout << "before lua_pcall栈长:" << lua_gettop(L) << endl;lua_call(L, 1, 1, 0);//lua_pcall(L, 1, 1, 0);cout << "after lua_pcall栈长:" << lua_gettop(L) << endl;cout<<"栈顶取返回值:" << lua_tonumber(L, -1) << endl;lua_close(L);
}
int lua_getglobal (lua_State *L, const char *name);
将全局名称的值推送到栈上。返回该值的类型。
void lua_call (lua_State *L, int nargs, int nresults);
调用一个函数,必须使用以下协议:
首先,将要调用的函数入栈;
然后,函数的参数按直接顺序入栈,
最后你调用lua_call;nargs是参数数量。nresults是返回值的数量。
调用函数时,所有参数和函数值都会出栈。函数的返回结果会按先后顺序入栈。
int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
在保护模式下调用函数。如果调用期间没有错误,lua_pcall的行为与lua_call完全相同。
六.Lua调用C函数
当C调用Lua函数时,它必须遵循一个简单的协议来传递参数并获得结果。同样,对于从Lua调用的C函数,它必须遵循协议来获取参数并返回结果。先来看一个demo
extern "C"
{
#include <Lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
#include <iostream>
using namespace std;static int AddOne(lua_State* L) {cout << "calling C Func AddOne" << endl;int d = lua_tonumber(L, 1); /* get argument */int ret = d + 1;lua_pushnumber(L, ret); /* push result */return 1; /* number of results */
}int main()
{lua_State* L = luaL_newstate();luaL_openlibs(L);lua_pushcfunction(L, AddOne);lua_setglobal(L, "myAdd");const char* lua_script = "print(myAdd(10))";luaL_dostring(L, lua_script);lua_close(L);
}
任何在Lua中注册的函数都必须具有相同的原型,在Lua.h中定义为Lua_CFunction:
接收lua_state*为参数,返回一个整数,代表返回值的数量
typedef int (*lua_CFunction) (lua_State *L);
void lua_pushcfunction (lua_State *L, lua_CFunction f);
将C函数推送到栈上。此函数接收指向C函数的指针,并将函数类型的Lua值推送到栈上,当调用该函数时,将调用相应的C函数。
void lua_setglobal (lua_State *L, const char *name);
从栈中弹出一个值,并将其设置为全局名称的新值。
结论就是这两个函数搭配使用,完成C函数的注册后,Lua就可以调用了
七. API的错误处理
Lua中的所有结构都是动态的:它们会根据需要生长,并在可能的情况下再次收缩。这意味着在Lua中内存分配失败的可能性是普遍存在的。几乎任何行动都可能面临这种可能性。Lua没有在其API中为每个操作使用错误代码,而是使用异常来发出这些错误信号。这意味着几乎所有API函数都可能抛出错误而不是返回。当我们编写应用程序代码(C代码)时,我们必须提供一种方法来捕获这些错误。
如果不希望应用程序退出,即使在内存分配失败的情况下,也必须在保护模式下运行代码。Lua代码的大部分(或全部)通常通过调用Lua_pcall来运行;因此,它以保护模式运行。即使在内存分配失败的情况下,lua_pcall也会返回错误代码
参考:
Lua 5.3 Reference Manual