• 云风Blog:Lua C API 的正确用法
  • C读取和调用Lua文件的库:lua.h, lauxlib.h, lualib.h
  • 包括:读写Lua全局变量的函数、调用Lua函数的函数、运行Lua代码片段的函数、注册C函数然后可以在Lua中被调用的函数
  • C和Lua之间的数据交换,通过对栈上的值进行操作。栈的使用解决:Lua会自动进行垃圾回收,而C要求显示的分配内存单元;Lua中的动态类型和C的静态类型。
  • 压入元素
void lua_pushnil(lua_State *L);										//插入空值
void lua_pushboolean(lua_State *L, int bool);						//插入布尔值
void lua_pushnumber(lua_State *L, double n);						//插入double
void lua_pushlstring(lua_State *L, const char* s, size_t length);	//插入任意字符串
void lua_pushstring(lua_State *L, const char* s);					//插入带'\0'的字符串
  • 查询元素
    1表示第一个被压栈的元素;-1表示栈顶元素;当一个C函数返回后,Lua会清理他的栈,所以,有一个原则:永远不要将指向Lua字符串的指针保存到访问他们的外部函数中。
int lua_is*(lua_State *L, int index);		//检查一个元素是否指定类型(number,string,boolean,table)

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);
  • 其他堆栈操作
int lua_gettop(lua_State *L);					//返回堆栈中的元素个数
void lua_settop(lua_State *L, int index);		//设置栈顶为一个指定的值,多余值被抛弃,否则压入nil值
void lua_pop(lua_State *L, int n);				//lua_settop(L, -n - 1),从堆栈中弹出n个元素
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);		//从栈顶中弹出元素并将其设置到指定索引位置
  • 错误处理
    应用程序中(调用Lua的C代码)的错误处理:当Lua遇到类似内存分配失败的情况,大部分会抛出异常,调用panic函数退出应用;其余情况会忽略异常,在保护模式下运行代码(Lua通过调用lua_pcall来运行)。
    类库(被Lua调用的C函数)中的错误处理:C函数发现错误只要调用luaL_error函数,清理所有在Lua中需要被清理的,然后和错误信息一起回到最初执行lua_pcall的地方。
  • 表操作
void lua_gettable(lua_State *L, int idx);					//以栈顶元素为key值,获取指定索引的表的值到栈顶
void lua_getfield(lua_State *L, int idx, const char *k);	//获取指定索引的表对应key的值到栈顶
void lua_getglobal(lua_State *L, const char *name);			//等于lua_getfield(L, LUA_GLOBALSINDEX, (name))。获取全局表的变量到栈顶
void lua_settable(lua_State *L, int idx);					//以栈顶元素为value,栈顶下一元素为key,设置指定索引的表的值
void lua_setfield(lua_State *L, int idx, const char *k);	//弹出栈顶元素,并设置为指定索引的表对应key的值
void lua_setglobal(lua_State *L, const char *name);			//等于lua_setfield(L, LUA_GLOBALSINDEX, (name))。设置全局变量的值
void lua_rawgeti(lua_State *L, int idx, int n);				//获得idx索引的表以n为key的值
void lua_rawget(lua_State *L, int idx);						//获得idx索引的表以栈顶为key的值
void lua_rawseti(lua_State *L, int idx, int n);				//设置idx索引的表以n为key的值
void lua_rawset(lua_State *L, int idx);						//设置idx索引的表以栈顶下一个元素为key的值
  • 调用函数
    使用API调用函数的方法是很简单的:首先,将被调用的函数入栈;第二,依次将所有参数入栈;第三,使用lua_pcall调用函数;最后,从栈中获取函数执行返回的结果。
    在将结果入栈之前,lua_pcall会将栈内的函数和参数移除。如果lua_pcall运行时出现错误,lua_pcall会返回一个非0的结果,并将错误信息入栈(依然会先移除栈内函数和参数)
    错误处理函数需要在被调用函数和参数之前入栈,参数errfunc为错误函数在栈中的索引。一般错误返回LUA_ERRRUN。内存分配错误返回LUA_ERRMEM;在错误处理函数中出错返回LUA_ERRERR,这两种情况并不会调用错误处理函数。
int  lua_pcall(lua_State *L, int nargs, int nresults, int errfunc);		//调用栈顶函数,指定参数格式nargs,返回结果个数,nresults,和错误函数
  • 调用C函数
    Lua调用C函数,使用栈进行交互,用来交互的栈不是全局变量,每一个函数都有他自己的私有栈,第一个参数总是在这个私有栈的index = 1的位置。
    一个Lua库实际上是一个定义了一系列Lua函数的chunk,并将这些函数保存在适当的地方,通常作为table 的域来保存。LuaC库就是这样实现的。
    在宿主程序中调用C函数
//注册被Lua调用的C函数的格式,以a+b为例
static int func(lua_State *L)
{
	//从栈中获取函数参数并检查合法性,1表示Lua调用时的第一个参数,以此类推
	double arg1 = luaL_checknumber(L, 1);
	double arg2 = luaL_checknumber(L, 2);
	
	//将函数结果入栈,如果有多个返回值,可以多次入栈
	lua_pushnumber(L, arg1 + arg2);
	
	//返回值表示该函数的返回值的数量
	return 1;
}

int main()
{
	lua_State* L = luaL_newstate();
	luaL_openlibs(L);
	
	//注册被Lua调用的C函数,参数"add"表示Lua调用时使用的全局函数名,func为被调用的C函数
	lua_register(L, "add", func);
	luaL_dostring("print(add(1, 2))");
	
	lua_close(L);
	return 0;
}

C函数库

//注册被Lua调用的C函数的格式,以a+b为例,函数必须以C的形式被导出
extern "C" int func(lua_State *L)
{
	//从栈中获取函数参数并检查合法性,1表示Lua调用时的第一个参数,以此类推
	double arg1 = luaL_checknumber(L, 1);
	double arg2 = luaL_checknumber(L, 2);
	
	//将函数结果入栈,如果有多个返回值,可以多次入栈
	lua_pushnumber(L, arg1 + arg2);
	
	//返回值表示该函数的返回值的数量
	return 1;
}

static luaL_Reg mylibs[] = 
{
	{"add", func},
	{NULLL, NULL},
};

//注册mylibs库,Lua中使用该库的用法:testlibs.add(1, 2)
extern "C" __declspec(dllexport)
int luaopen_testlibs(lua_State* L) 
{
	luaL_newlib(L, mylibs);
	return 1;
}
  • 字符串处理
    C函数接受一个来自lua的字符串作为参数时,有两个规则必须遵守:当字符串正在被访问的时候不要将其出栈;永远不要修改字符串。
    C函数需要创建一个字符串返回给lua的时候,C代码负责缓冲区的分配和释放,负责处理缓冲溢出等情况。
void lua_concat(lua_State *L, int n);		//连接栈顶开始的n个字符串,弹出这n个字符串并压栈结果
const char *lua_pushfstring(lua_State *L, const char *fmt, ...);	//根据格式串fmt的要求创建一个新的字符串。
  • 注册表
    使用LUA_REGISTRYINDEX索引来保存注册表中的Lua值。任何C库都可以在这张表里保存数据, 为了防止冲突,可以使用保护库名前缀的名字作为key值。注册表中的整数key用于应用机制luaL_ref
  • 闭包
    一个C函数和它的upvalues的组合称为闭包。upvalues为函数能访问的外部局部变量。
static int counter(lua_State *L)
{
	double val = lua_tonumber(L, lua_upvalveindex(1));
	lua_pushnumber(L, ++val);
	lua_pushvalue(L, -1);
	lua_replace(L, lua_upvalueindex(1));
	return 1;
}

int newCounter(lua_State *L)
{
	lua_pushnumber(L, 0);
	//第二个参数为基本函数,第三个参数是upvalues的个数
	lua_pushcclosure(L, &counter, 1);
	return 1;
}
  • Userdata
void *lua_newuserdata(lua_State *L, size_t size);		//按照指定size的大小分配一段内存放入栈内,并返回这个地址
  • Metatable
int luaL_newmetatable(lua_State *L, const char* tname); 			//栈顶创建metatable,建立表和registry中类型名的双向关系(分别以tname为表的key和以表为tname的key)
void luaL_getmetatable(lua_State *L, const char* tname); 			//获取registry中tname对应的表
void* luaL_checkudata(lua_State *L, int index, const char* tname);	//检查指定索引的元素是否为带有给定tname的metatable的useratum,如果是,返回useratum的地址,否则返回NULL