引言
skynet 和 openresty 都是深度使用lua的典范,学习lua不经要学会基本语法,还要学会C语言与Lua交互。lua的一大优点就是能和c/c++无缝连接,而且可以在不需要重复编译c/c++的情况下可以修改lua文件并且起作用,当我们的项目文件很大的时候,使用lua进行项目修改极大的减少了等待时间。
Lua由标准C编写而成,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。
Lua C接口编程我会分为两个部分来介绍,第一部分介绍C/C++如何调用Lua,第二部分介绍Lua如何调用C/C++。
一、Lua虚拟栈
- 栈中只能存放Lua类型的值,如果想保存C类型的值,需要先将C类型转换为Lua类型;
- Lua每次调用C函数都会生成新的栈,独立于之前的栈
- C在创建虚拟机是,伴随创建一个主协程和一个虚拟栈
- 无论何时Lua再调用C时,只保证至少有LUA_MINSTACK大小的堆栈空间可以使用。LUA_MINSTACK一般定义为20,只要你不是一致压栈,通常不用担心堆栈大小。
Lua中的栈有两排索引,正数1索引的位置在栈底,负数索引-1在栈顶,这样做的好处是不需要知道栈的大小,只需要查找正负索引1的位置就能确定栈顶和栈底的位置。
注意:C 在调用lua的时候要注意虚拟堆栈的变化情况,要保持堆栈在函数执行前后不变,养成良好的习惯。
二、注册表
2.1 什么是注册表
有时候,我们需要在程序中使用一些非局部的变量。在C中我们可以使用全局变量或是静态变量来实现,而在为Lua编写C库的过程中,使用以上类型的变量并不是一个好的方式,原因如下:
- 这些变量中无法存储Lua的值
- 这些变量如果在多个Lua虚拟机中被使用,很可能造成不可预期的结果。
一个替代方案是,将这些值存储在Lua的全局变量中。这种方式解决了上面提到的两个问题,Lua全局变量可以存储任何Lua的值,同时每一个Lua状态机都有自己独立的一套全局变量。但这依旧不是最好的方式,因为是Lua的全局变量,Lua程序可以随意的修改变量的值,这很可能对C库中的函数在使用这些变量时造成影响。
为了进一步避免上述情况,Lua提供了一张特殊的表(table),它可以供C代码随意使用。但是对于Lua代码,访问却是被禁止的。这个特殊的表便是注册表。
2.2 伪索引 pseudo-index
伪索引跟虚拟栈中正常的索引类似,区别在于,虽然使用它也是通过虚拟栈,但其所对应的值并不是存储在虚拟栈中。比如:LUA_REGISTRYINDEX 就是一个伪索引,定义在”lua.h”中。它用于通过虚拟栈访问注册表(但注册表并非实际存储在虚拟栈中),使用时按照虚拟栈中正常索引的使用方式使用。
2.3 小结
用来在多个C库中共享Lua数据,包括userdata 和 lightuserdata等;
- 一张预定义的表,用来保存任何C代码想保存的Lua值;
- 使用 LUA_REGISTRYINDEX来索引;
- 比如:获取skynet_context
三、Lua 常用API介绍
lua_State* L=luaL_newstate(); -- 函数返回一个指向堆栈的指针 luaL_openlibs(L); -- 打开指定状态机中的所有 Lua 标准库,也就是把所有标准类库加载到指定的虚拟机. lua_createtable(L,0,0); -- 新建并压入一张表 lua_pushstring(L,0,0); -- 压入一个字符串 lua_pushnumber(L,0,0); -- 压入一个数字 lua_tostring(L,1); -- 取出一个字符串 lua_tointeger(L,1); -- 取出数字 double b =lua_tonumber(); -- 取出一个double类型的数字 lua_load()函数 -- 当这个函数返回0时表示加载 luaL_loadfile(filename) -- 这个函数也是只允许加载lua程序文件,不执行lua文件。它是在内部去用lua_load()去加载指定名为filename的lua程序文件。当返回0表示没有错误。 luaL_dofile() -- 这个函数不仅仅加载了lua程序文件,还执行lua文件。返回0表示没有错误。 lua_push*(L,data) -- 压栈, lua_to*(L,index) -- 只取值, 不出栈 lua_pop(L,count) -- 出栈。 lua_close(L); -- 释放lua资源 lua_call(L,0,0,0); -- 调用对应函数 lua_pcall(L,0,0,0); -- 安全的调用对应函数,最后一个参数是错误处理函数
除了上面的API,我们着重看下面几个API:
- lua_getglobal(L, funcname)
lua_getglobal是个宏,每次调用这个宏的时候,都会将Lua代码中funcname与之相应的全局变量值压入栈中。 - lua_setfield(L, LUA_REGISTRYINDEX, “LUA_NOENV”);
等价于执行 LUA_REGISTRYINDEX[LUA_NOENV] = stackTopElem 操作, 其中 stackTopElem指当前栈顶元素。 - lua_getfield(L, LUA_REGISTRYINDEX, “LUA_NOENV”);
将LUA_REGISTRYINDEX[LUA_NOENV] 压入栈,返回入栈值的类型 - int luaL_ref(lua_State L, int t)
将栈顶的元素出栈作为value,从虚拟栈的索引 t 处获取到 table ,之后相当于执行"table[key] = value"的操作。函数返回生成的唯一的key,程序可以通过该key获取到对应的value。如果 value 为 nil ,则不会生成key,函数返回 LUA_REFNIL。 - void luaL_unref(lua_State L, int t, int ref);
从虚拟栈的索引 t 处获得 table,之后做等价于执行 table[ref] = nil 操作。如果 ref 是LUA_NOREF或LUA_REFNIL,则函数不做任何操作。 - lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
从注册表通过key(ref)获取到对应的value(foo函数),并将其入栈
使用 luaL_ref 和 luaL_unref 时无需关心如何创建唯一的”key”,便可以在注册表中自由的存取数据。
代码示例
示例1:常用api用法
C源文件test_field.c
#include <stdio.h> #include <stdlib.h> #include <lua.h> #include <lauxlib.h> #include <lualib.h> /* Lua的异常捕获主要基于pcall及xpcall函数。 */ void getStackSize(const char * desc, const int count) { printf("%s stack size = [ %d ]\n",desc, count); } void test_api_getfield() { lua_State *L = luaL_newstate(); // 加载并执行目标lua文件 if ( LUA_OK != luaL_dofile(L, "test_field.lua") ) { const char* err = lua_tostring(L, -1); fprintf(stderr, "err:\t%s\n", err); return ; } lua_getglobal(L,"tab"); // 查找 tab 变量压入栈底 // lua_gettop 获取栈中元素数量 getStackSize("stage 1", lua_gettop(L)); // lua_getfield(L, -1, "a"); // 将 tab.a 入栈 int nTab_a = lua_tointeger(L,-1); // 将 tab.a 取出赋值给变量nTab_a lua_getfield(L, -2, "b"); // 将 tab.b 入栈 int nTab_b = lua_tointeger(L,-1); // 将 tab.b 取出赋值给变量nTab_b getStackSize("stage 2",lua_gettop(L)); lua_pop(L, 3); // 清除掉栈中多余的3个变量tab、tab.a、tab.b getStackSize("stage 3",lua_gettop(L)); int nTab_c = 2 * nTab_a + nTab_b; lua_pushinteger(L, nTab_c); // 将 c = 2a + b 计算完成,压入栈顶 printf("nTab_c = %d \n", nTab_c); // 输出: 5 getStackSize("stage 4",lua_gettop(L)); lua_getglobal(L,"lua_func"); // 查找lua_func函数并将其压入栈底 lua_pushinteger(L, 3); // 压入函数变量 x=3 getStackSize("stage 5",lua_gettop(L)); // lua_pcall 在保护模式下调用一个函数,防止异常程序直接退出 lua_pcall(L,1,1,0); // 执行脚本函数lua_func getStackSize("stage 6",lua_gettop(L)); int result = lua_tointeger(L,-1); // 从栈中取回返回值 getStackSize("stage 7",lua_gettop(L)); printf("res = %d \n", result); lua_pop(L,1); // 弹出返回结果 getStackSize("stage 8",lua_gettop(L)); lua_close(L); //关闭lua环境 } int main(int argc, char** argv) { test_api_getfield(); return 0; }
lua文件test_fielf.lua
-- tab 表 tab = { a = 2, b = 1 } -- 全局变量c c = 100; function lua_func(x) -- print("lua c: ", c) return (tab.a * x * x + tab.b * x + c) end
编译方法:
gcc -g -o test_field test_field.c -llua -ldl -lm
运行结果:
示例2:luaL_ref 、luaL_unref、lua_rawgeti 应用
C源文件test_reg_ref.c
#include <stdlib.h> #include <lua.h> #include <lualib.h> #include <lauxlib.h> int main() { lua_State *L = luaL_newstate(); luaL_openlibs(L); if ( LUA_OK != luaL_dofile(L, "test_reg_ref.lua") ) { const char* err = lua_tostring(L, -1); fprintf(stderr, "err:\t%s\n", err); return 1; } lua_getglobal(L,"foo"); printf("stack 1 size : %d, %d\n", lua_gettop(L), lua_type(L,-1)); // 从堆栈顶弹出foo函数,存放到注册表中并返回引用 // luaL_ref 返回一个int的值。这个返回值就是对应的foo函数的 key int ref = luaL_ref(L, LUA_REGISTRYINDEX); printf("stack 2 size : %d\n", lua_gettop(L)); // lua_rawgeti(L,LUA_REGISTRYINDEX,ref); 可以从注册表通过key(ref)获取到对应的value(foo函数) // 将该函数放入堆栈 lua_rawgeti(L, LUA_REGISTRYINDEX, ref); printf("stack 3 size : %d, %d\n", lua_gettop(L), lua_type(L,-1)); // 调用foo函数后 foo出栈 lua_pcall(L,0,0,0); printf("stack 4 size : %d\n", lua_gettop(L)); printf("----------------分割线 1------------------\n"); // 继续将foo压入堆栈 lua_getglobal(L,"foo"); printf("stack 5 size : %d\n", lua_gettop(L)); lua_setfield(L, LUA_REGISTRYINDEX, "fooKey"); // 相当于:LUA_REGISTRYINDEX[fooKey] = foo; printf("stack 6 size : %d\n", lua_gettop(L)); lua_getfield(L, LUA_REGISTRYINDEX, "fooKey"); // 通过 fooKey 从 LUA_REGISTRYINDEX拿到foo 并入压栈 printf("stack 7 size : %d, %d\n", lua_gettop(L), lua_type(L,-1)); lua_pcall(L,0,0,0); // foo call printf("stack 8 size : %d, %d\n", lua_gettop(L), lua_type(L,-1)); printf("----------------分割线 2------------------\n"); // 一旦ref在注册表的引用解除,就无法继续通过key(ref)这个引用获取到 value(即foo函数) luaL_unref(L, LUA_REGISTRYINDEX, ref); // 从注册表解除后,lua_rawgeti无法再通过key(ref) 找到foo lua_rawgeti(L, LUA_REGISTRYINDEX, ref); printf("stack 9 size : %d, %d\n", lua_gettop(L), lua_type(L,-1)); // 通过 fooKey 还是可以继续从LUA_REGISTRYINDEX拿到foo 并入压栈 lua_getfield(L,LUA_REGISTRYINDEX,"fooKey"); printf("stack 10 size : %d\n", lua_gettop(L)); lua_pcall(L,0,0,0); printf("---------------- end -----------------\n"); lua_close(L); return 0; }
Lua文件test_reg_ref.lua
function foo() print("test_reg_ref foo call") end
编译方法:
gcc -g -o test_reg_ref test_reg_ref.c -llua -ldl -lm
运行结果: