Lua 与 C 交互
lua 是一种嵌入语言,并不能独立运行,而是一个可以链接到其它应用程序的库。lua 是使用 c 语言开发的,因此它与 c 语言的交互非常简单,但 lua 作为一门十分流行的脚本语言,不仅能嵌入到语言中,还能嵌入到 java,c# 等其它语言中。lua 与 c 语言的交互方式有两种,一种是以 c 语言为主,lua 作为 c 语言调用的一个库,这种方式的 c 代码称为 应用程序代码,另一种是以 lua 为主,c 语言作为 lua 调用的一个库,这种方式的 c 代码称为 库代码。这两种方式使用同一套 API 来实现,称为 C API。
当 lua 与 c 进行交互时,需要用到下面几个头文件
* lua.h/lua.hpp 定义了 lua 的基础函数,包括创建环境,调用 lua 函数,读写 lua 环境全局变量,注册供 lua 调用的函数等。这些基础函数以 lua_ 开头
* lualib.h 定义了打开各种标准库的函数
* lauxlib.h 是一个辅助库,是使用 lua.h 中的 API 编写的一个较高的抽象层,所有定义以 luaL_ 开头
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include <stdio.h>
#include <string.h>
int main()
{
char buff[255];
int error;
//创建lua新环境
lua_State* L = luaL_newstate();
//加载lua标准库
luaL_openlibs(L);
while (fgets(buff, sizeof(buff), stdin) != NULL)
{
error = luaL_loadbuffer(L, buff, strlen(buff), "line") || lua_pcall(L, 0, 0, 0);
if (error)
{
fprintf(stderr, "%s", lua_tostring(L, -1));
lua_pop(L, 1);
}
}
lua_close(L);
return 0;
}
在 c 程序中调用 lua 函数的步骤
1. 使用 luaL_newstate()
创建一个 lua 环境,这个新环境没有任何预定义的函数,是一个空的环境
2. 使用 luaL_openlibs()
打开所有 lua 标准库
3. 使用 luaL_loadbuffer()
编译 lua 代码块,然后将编译后的程序块压入栈
4. 使用 lua_pcall()
将程序块从栈中弹出,并在保护模式下运行
5. 如果发生错误,lua 会把错误信息压入栈中,使用 lua_tostring
从栈中取错误信息,然后使用 lua_pop()
把信息从栈中删除
6. 使用 lua_close
关闭 lua 环境
栈
lua 和 c 的交互都是通过一个神奇的栈,首先,lua 与 c 交互需要解决两个问题
第一,c 语言是静态数据类型,lua 语言是动态数据类型
第二,c 语言没有自动内存回收机制,而 lua 语言有内存回收机制
为了解决这两个问题,lua 使用了一个栈,无论是 c 语言把参数传给 lua 还是从 lua 获取参数,都先把参数压入到栈中,然后再从栈取出,这样就需要为 c 语言中不同的数据类型设计不同的压栈和出栈操作函数。另外,这个栈是 lua 管理的,因此垃圾器能够知道 c 语言使用了哪些值,就能够正常地进行垃圾回收了
压入元素
C API 定义了一组函数,这组函数可以将各个类型的 c 变量值压入到 lua 栈中,这种函数的形式为 lua_pushtype。
lua_pushnil(L);
int b = 0;
lua_pushboolean(L, b);
lua_Integer i = 10;
lua_pushinteger(L, i);
lua_Number num = 3.14;
lua_pushnumber(L, num);
const char* str = "hello";
lua_pushlstring(L, str, strlen(str));
lua_pushstring(L, str);
查询元素
lua 的栈遵循栈的 先进后出 规则,栈中元素的索引有两种记录方式。第一种,栈底索引为 1,往上索引递增;第二种,栈顶元素索引为 -1,往下索引递减。
C API 定义了一组函数,这组函数可以查询 lua 栈中某个索引位置的值是否为 c 语言中的某个类型,形式为 lua_istype。判断的准则是能否进行类型转换,比如数字转成字符串是肯定成功的,因此对于一个数字,lua_isstring() 一定会返回真。另外,也可以使用 lua_type() 来获取栈中元素的具体类型。
printf("index 1 is nil? %d\n", lua_isnil(L, 1)); //true
printf("index 2 is boolean? %d\n", lua_isboolean(L, 2)); //true
printf("index 3 is integer? %d\n", lua_isinteger(L, 3)); //true
printf("index -1 is string? %d\n", lua_isstring(L, -1)); //true
printf("index -2 is number? %d\n", lua_isnumber(L, -2)); //false
printf("index -3 is string? %d\n", lua_isstring(L, -3)); //true
printf("index -3 is number? %d\n", lua_isnumber(L, -3)); //true
printf("index -4 is number? %d\n", lua_isnumber(L, -4)); //true
printf("index 4 is integer? %d\n", lua_isinteger(L, 4)); //false
经过上面的压栈操作后,栈中的元素从栈顶往栈底依次是
“hello” “hello” 3.14 10 0 nil
分析上面的输出,nil 0 10 依次可以转成 nil 类型,boolean 类型和 int 类型,因此前三条语句输出真。索引 -1 到 -4 对应值的类型是 string string number int;第五条语句 string 不能转成 number,因此返回假,最后一条语句 number 不能转成 int,因此返回候,其它语句都返回真。
获取元素
C API 定义了一组函数,这组函数从 lua 栈中获取一个元素并转成相应的 c 数据类型,形式为 lua_totype()。如果不能进行类型转换,lua 并不会出错,而是返回一个默认值;lua_toboolean lua_tointeger lua_tonumber lua_toobjlen 会返回 0,而其它函数会返回 NULL。因此在调用 lua_totype 前调用 lua_istype 不是必需的。
printf("index 1 value: %d\n", lua_tonumber(L, 1)); //0
printf("index 1 value: %s\n", lua_tostring(L, 1)); //null
printf("index 2 value: %d\n", lua_toboolean(L, 2)); //0
printf("index 3 value: %d\n", lua_tointeger(L, 3)); //10
printf("index -1 value: %s\n", lua_tostring(L, -1)); //"hello"
printf("index -2 value: %s\n", lua_tolstring(L, -2, NULL)); //"hello"
printf("index -3 value: %.2f\n", lua_tonumber(L, -3)); //3.14
size_t len;
const char* s;
s = lua_tolstring(L, 6, &len);
printf("index 6 value: %s, length: %d\n", s, len); //"hello" 5
其它栈操作
- lua_gettop(lua_State*) 获得栈顶元素索引,即栈中元素的个数
- lua_settop(lua_State*,int) 设置栈顶元素索引,即设置栈中元素个数,原来栈中多出的元素被丢弃,不足在栈顶补 nil。索引可以设置成负数,比如设成 -2,表示从栈中删除一个元素(原来是 -1)。如果索引设置成 0 表示清空栈。注意此函数的功能设置栈中元素个数,设置完成后栈顶的索引会恢复正常,比如把栈顶索引调成 -3,会从栈顶删除两个元素,删除后栈索引会恢复成 -1
- lua_pop(lua_State*,int n) 从栈顶弹出 n 个元素,等价于 lua_settop(lua_State*,-1-n)
- lua_pushvalue(lua_State*,int) 将指定索引的值副本压入栈
- lua_remove(lua_State*,int) 将指定索引的元素删除,上面的元素下移
- lua_insert(lua_State*,int) 将栈顶元素移动到指定索引,上面的元素上移
- lua_replace(lua_State*,int) 弹出栈顶元素,并替换指定索引位置的元素
使用下面这个函数从栈底往栈顶输出栈中所有元素
void stackDump(lua_State* L)
{
int type;
//栈元素个数
int top = lua_gettop(L);
for (int i = 1; i <= top; i++)
{
type = lua_type(L, i);
switch (type)
{
case LUA_TBOOLEAN:
{
printf("%s ", lua_toboolean(L, i) == 0 ? "false" : "true");
break;
}
case LUA_TNIL:
{
printf("nil ");
break;
}
case LUA_TNUMBER:
{
/*if (lua_isinteger(L, i))
{
printf("%d ", lua_tointeger(L, i));
}
else
{
printf("%.2f ", lua_tonumber(L, i));
}*/
printf("%g ", lua_tonumber(L, i));
break;
}
case LUA_TSTRING:
{
printf("%s ", lua_tostring(L, i));
break;
}
default:
printf("%s ", lua_typename(L, i));
break;
}
}
printf("\n");
}
经过下面系列栈操作后栈中元素的结果如下
//操作前
stackDump(L);
//把某个索引的值压入栈
lua_pushvalue(L, 3);
stackDump(L);
//弹出两个元素
lua_pop(L, 2);
stackDump(L);
//将栈顶元素插入到位置2
lua_insert(L, 2);
stackDump(L);
//使用栈顶元素替换栈底元素
lua_replace(L, 1);
stackDump(L);
//移除栈底元素
lua_remove(L, 1);
stackDump(L);
//弹出两个元素
lua_settop(L, -3);
stackDump(L);
nil false 10 3.14 hello hello
nil false 10 3.14 hello hello 10
nil false 10 3.14 hello
nil hello false 10 3.14
3.14 hello false 10
hello false 10
hello