分下面几种函数:
- 读写Lua全局变量的函数
- 调用Lua函数的函数
- 运行Lua代码片断的函数
- 注册C函数然后可以在Lua中被调用的函数
一、一个简单的Lua解释器
```cinclude
include
// Lua提供的基础函数 include
// 辅助库(auxlib)提供的函数,都是luaL_开头,是lua.h的基础上的封装(抽象) include
// 定义了打开各种库的api
int main (void) { char buff[256]; int error;
// 创建一个新的Lua环境environment(全局表),几乎是空的,连print都没有
// 通过lualib.h的接口,打开各种库,注册到环境table中。
lua_State *L = lua_open();
luaopen_base(L); // 基础库
luaopen_table(L); // table库,这样lua中才能使用table
luaopen_io(L); // io库,lua中才能使用io.write之类的
luaopen_string(L); // string库
luaopen_math(L); // math库
while (fgets(buff, sizeof(buff), stdin) != NULL) {
error = luaL_loadbuffer(L, buff, strlen(buff), // 遍历lua代码类似loadstring
// 成功,就把chunk压栈
// 错误,就把错误消息压栈
"line") || lua_pcall(L, 0, 0, 0); // 弹出栈顶lua chunk并执行(保护模式)
if (error) {
fprintf(stderr, "%s", lua_tostring(L, -1)); // 栈顶数据转成lua string
lua_pop(L, 1); // 弹出栈顶错误消息
}
}
lua_close(L);
return 0;
}
<a name="ZOSoK"></a>
# 二、C和Lua交互栈
为什么会用到栈?Lua和C之间交互会出现的问题。<br />1、变量类型匹配问题<br />Lua是动态类型,每个变量的类型运行时会改变。C肯定不能这样,定义了就固定了。<br />2、内存管理问题<br />Lua是有自动内存管理,C堆内存非自动,可能Lua那边已经回收,C这边还在。<br />通过栈来保存Lua值,栈由Lua管理,C通过调用Lua来将C类型值压栈,然后出栈数据给Lua使用。Lua以LIFO原则来操作栈,也就是说只改变栈顶函数。
<a name="dOzGt"></a>
## 操作栈:压栈、查询
API通过索引来访问栈元素,索引规则:<br />正数表示法:1表示第一个压栈元素,也就是**栈底**,2是往上一个。<br />负数表示法:-1表示最后一个压栈元素,也就是**栈顶**,-2是往下一个。
```c
void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, double n);
//lua字符串不是以0作为结尾,而必须指明长度。
//lua对栈中的字符串,只拷贝,不会以指针来操作。
void lua_pushlstring (lua_State *L, const char *s, size_t length);
void lua_pushstring (lua_State *L, const char *s);
//检查栈是否有能存储sz条记录的空间
//lua.h中定义了栈最小空闲空间LUA_MINSTACK
int lua_checkstack (lua_State *L, int sz);
//index位置的栈元素是否是目标类型。如lua_isnumber,lua_isstring,lua_istable
//lua_isnumber和lua_isstring通过是否能转换来判断。
//对下面lua_type的封装
int lua_is... (lua_State *L, int index);
//栈顶元素是否是type类型的lua值
//enum_type值如下
//LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER, LUA_TSTRING
//LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD
int lua_type (lua_State *L, enum_type type);
//从栈中获取元素,注意没有弹栈
int lua_toboolean (lua_State *L, int index); //失败返回0
double lua_tonumber (lua_State *L, int index); //失败返回0
const char * lua_tostring (lua_State *L, int index); //失败返回0
size_t lua_strlen (lua_State *L, int index); //失败返回NULL
//lua字符串不是0结尾,而C是,所以C操作从栈中返回的string要特别注意
const char *s = lua_tostring(L, -1); //获取luastring
size_t len_lua = lua_strlen(L, -1); //s的真实长度
size_t len_c = strlen(s); //c以字符串操作s这是不对的,s中间可能有0字符,
//len_c <= len_lua
int lua_gettop (lua_State *L); //返回栈元素个数,栈顶整数索引
void lua_settop (lua_State *L, int index); //超过index的被丢失,不足的填nil,index=0清空栈
#define lua_pop(L,n) lua_settop(L, -(n)-1) //丢失栈顶的n个元素
void lua_pushvalue (lua_State *L, int index); //拷贝index元素,压入栈顶。
void lua_remove (lua_State *L, int index); //移除index元素
void lua_insert (lua_State *L, int index); //栈顶元素插入到index位置,上面的上移。
void lua_replace (lua_State *L, int index); //弹出栈顶,并替换index位置的值。
打印栈
// 从栈底到栈顶遍历了整个堆栈,依照每个元素自己的类型打印出其值。它用引号输出字符串;
// 以%g的格式输出数字;对于其它值(table,函数,等等)
// 它仅仅输出它们的类型(lua_typename转换一个类型码到类型名)
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 */
}
三、C调用Lua
C调用Lua原理比较简单。
1、由lua端设计了栈,并给C提供了栈操作的API
2、通过API,C可以直接将Lua当前环境下的变量入栈。
3、也就很容易让函数、变量压栈,并执行函数,返回值、错误信息再压栈,供C端使用。
C使用Lua配置
场景:C是一个窗口程序,用Lua做颜色主题的配置文件。
具体方法:Lua是用一个全局表做配置文件,C封装直接获取/设置表的域值的函数。
------------颜色主题配置文件------------
WHITE = { r = 1, g = 1, b = 1}
RED = { r = 1, g = 0, b = 0}
background = WHITE
/**********C中预定义的颜色主题配置************/
#define MAX_COLOR 255
struct ColorTable {
char *name;
unsigned char red, green, blue;
} colortable[] = {
{"WHITE", MAX_COLOR, MAX_COLOR, MAX_COLOR},
{"RED", MAX_COLOR, 0, 0},
{"GREEN", 0, MAX_COLOR, 0},
{"BLUE", 0, 0, MAX_COLOR},
{"BLACK", 0, 0, 0},
...
{NULL, 0, 0, 0}
};
/******************************************/
/*********C封装backgroud[key]操作********/
//假设当前栈顶元素为backgroud
int getfield (const char *key) { //假设栈顶已经是backgroud
int result;
lua_pushstring(L, key); //key压栈
lua_gettable(L, -2); //key出栈,background[key]压栈
if (!lua_isnumber(L, -1)) //检查数值合法性
error(L, "invalid component in background color");
result = (int)lua_tonumber(L, -1) * MAX_COLOR;
lua_pop(L, 1); //栈顶弹出
return result;
}
/*********C封装backgroud[key] = value操作********/
//假设栈顶是background
void setfield (const char *index, int value) {
lua_pushstring(L, index);
lua_pushnumber(L, (double)value/MAX_COLOR);
lua_settable(L, -3); //栈顶是value,栈顶-1是key,栈顶-2是background
//background[key]=value,key,value弹栈。
}
/*********C封装backgroud = {r=1,g=2,b=3}操作********/
//假设栈顶是background
void setcolor (struct ColorTable *ct) {
lua_newtable(L); //创建表table
setfield("r", ct->red); /* table.r = ct->r */
setfield("g", ct->green); /* table.g = ct->g */
setfield("b", ct->blue); /* table.b = ct->b */
lua_setglobal(ct->name); /* 'name' = table */
}
/********************封装函数使用***********************/
/*********把上面C中预定义的常见颜色值加载到lua全局表_G中**********/
int i = 0;
while (colortable[i].name != NULL)
setcolor(&colortable[i++]);
/*********C中获取上面lua配置中的background表的r、g、b值**********/
lua_getglobal(L, "background"); //全局变量background入栈
if (!lua_istable(L, -1)) //检查结果
error(L, "`background' is not a valid color table");
red = getfield("r"); //backgroud.r
green = getfield("g"); //backgroud.g
blue = getfield("b"); //backgroud.b
C调用Lua函数
场景:C写王者荣耀,Lua计算伤害数值。
--------------Lua函数
function f (x, y)
return (x^2 * math.sin(y))/(1 - x)
end
/* 调用Lua的f函数 */
double f (double x, double y) {
double z;
lua_getglobal(L, "f"); //全部变量f(函数)压栈
lua_pushnumber(L, x); //参数x压栈
lua_pushnumber(L, y); //参数y压栈
//多使用pcall,安全。还有错误处理函数
if (lua_pcall(L, 2, 1, 0) != 0) //调用栈中函数,2个参数,1个返回值,没有错误处理函数
//返回值,错误信息入栈
error(L, "error running function `f': %s",lua_tostring(L, -1)); //输出错误信息
//检查返回值数据类型
if (!lua_isnumber(L, -1)) error(L, "function `f' must return a number");
z = lua_tonumber(L, -1); //获取返回值
lua_pop(L, 1); //返回值出栈
return z;
}
C动态调用Lua函数
动态调用lua函数,函数名,参数类型,参数数量,返回值都运行时决定。
//local ret = f(x,y)
//dd>d:double f(double, double)
call_va("f", "dd>d", x, y, &z);
#include <stdarg.h>
void call_va (const char *func, const char *sig, ...) {
va_list vl;
int narg, nres; //参数数量,返回值数量
va_start(vl, sig);
lua_getglobal(L, func); //全局函数func
narg = 0;
//根据sig字符串,参数入栈。
while (*sig) {
switch (*sig++) { //"dd>d":>前面部分
case 'd':
lua_pushnumber(L, va_arg(vl, double));
break;
case 'i':
lua_pushnumber(L, va_arg(vl, int));
break;
case 's':
lua_pushstring(L, va_arg(vl, char *));
break;
case '>':
goto endwhile;
default:
error(L, "invalid option (%c)", *(sig - 1));
}
narg++;
luaL_checkstack(L, 1, "too many arguments"); //检查栈空间
} endwhile:
nres = strlen(sig); //检查返回值数量与sig字符串标识的是否对得上
if (lua_pcall(L, narg, nres, 0) != 0) //调用函数
error(L, "error running function `%s': %s", func, lua_tostring(L, -1));
nres = -nres; //第一个返回值
while (*sig) {
switch (*sig++) { //"dd>d":>后面部分
case 'd':
if (!lua_isnumber(L, nres)) error(L, "wrong result type");
*va_arg(vl, double *) = lua_tonumber(L, nres);
break;
case 'i': /* int result */
if (!lua_isnumber(L, nres)) error(L, "wrong result type");
*va_arg(vl, int *) = (int)lua_tonumber(L, nres);
break;
case 's': /* string result */
if (!lua_isstring(L, nres)) error(L, "wrong result type");
*va_arg(vl, const char **) = lua_tostring(L, nres);
break;
default:
error(L, "invalid option (%c)", *(sig - 1));
}
nres++;
}
va_end(vl);
}
四、Lua调用C函数
必须先注册函数,即把C函数地址以适当方式传递给Lua解释器,函数指针就可以啦。
注册的过程,其实就是C端通过栈把C函数的指针传给lua的环境变量中。lua_pushcfunction
实例
第一个例子,注册 sin函数给lua调用,最简单粗暴的注册。
// 这是所有注册的C函数的格式,
// 函数指针格式,将指针压栈传给lua(function类型),lua即可定位到函数地址,进行调用。
// int: 返回值个数,这个是要告诉Lua的,这样Lua才知道从栈中如何获取返回值。
// L: lua当前环境,必须以这个值做第一个参数。
typedef int (*lua_CFunction) (lua_State *L);
//注册的C函数
static int l_sin (lua_State *L) {
double d = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(d));
return 1; /* number of results */
}
lua_pushcfunction(l, l_sin); // 函数直接压栈,最简单粗暴的方式
lua_setglobal(l, "mysin"); // lua中mysin = l_sin
第二个例子,注册一个目录展示的函数给lua,lua标准没有此类函数。
#include <dirent.h>
#include <errno.h>
static int l_dir (lua_State *L) {
DIR *dir;
struct dirent *entry;
int i;
const char *path = luaL_checkstring(L, 1);
/* open directory */
dir = opendir(path);
if (dir == NULL) { /* error opening the directory? */
lua_pushnil(L); /* return nil and ... */
lua_pushstring(L, strerror(errno)); /* error message */
return 2; /* number of results */
}
/* create result table */
lua_newtable(L);
i = 1;
while ((entry = readdir(dir)) != NULL) {
lua_pushnumber(L, i++); /* push key */
lua_pushstring(L, entry->d_name); /* push value */
lua_settable(L, -3);
}
closedir(dir);
return 1; /* table is already on top */
}
注册C函数库
设计一个lua函数库给C使用很简单,一个table搞定。
C可以借助辅助函数库lauxlib.h,批量设计C函数然后注册到一个lua table中。
以下是注册函数库名mylib的例子。mylib中有一个函数l_dir。
/*******************mylib.h********************/
int luaopen_mylib (lua_State *L); //打开函数库方法(批量注册函数)
//当解释器创建新的状态的时候会调用这个宏
#define LUA_EXTRALIBS { "mylib", luaopen_mylib },
/*************库中的待注册函数*************/
static int l_dir (lua_State *L) {
...
}
/*************mylib.cpp*************/
static const struct luaL_reg mylib [] = { //name-function数组,批量注册
//两个元素的结构体。
{"dir", l_dir}, //dir:函数名,l_dir:C函数指针
{NULL, NULL} //必须以这个结尾,才能识别结束
};
int luaopen_mylib (lua_State *L) {
luaL_openlib(L, //辅助函数帮助批量注册
"mylib", //库名称,在lua的当前环境下创建或者reuse一个叫mylib的表
mylib, //库函数数组,name-function
0 //upvalue,
);
return 1; //传给lua,返回值个数,
}
/*******************Lua代码************************/
//1、以动态链接库的方式,由lua动态加载。
//2、跟主C代码一起编译,在C代码逻辑中注册。
mylib = loadlib("fullname-of-your-library", //链接库路径
"luaopen_mylib" //打开库的函数
)
mylib() //打开库
写C函数技巧:数组操作
数组会被经常用于排序,这涉及大了高频次的访问数组元素,提供专门优化的API很有必要。
// index是负值索引时,lua_rawgeti(L,index,key)
// 等价于
// lua_pushnumber(L, key);
// ua_rawget(L, index);
void lua_rawgeti (lua_State *L, int index, int key);
//index是负值索引时,lua_rawseti(L, index, key)
// 等价于
//lua_pushnumber(L, key);
//lua_insert(L, -2); /* put 'key' below previous value */
//lua_rawset(L, index);
void lua_rawseti (lua_State *L, int index, int key);
/*******************例子***********************/
//以数组的每一个元素为参数调用一个指定的函数,并将数组的该元素替换为调用函数返回的结果
int l_map (lua_State *L) {
int i, n;
luaL_checktype(L, 1, LUA_TTABLE); //第一个参数必须是table
luaL_checktype(L, 2, LUA_TFUNCTION); //第二个参数必须是function
n = luaL_getn(L, 1); //数组大小
for (i = 1; i <= n; i++) {
lua_pushvalue(L, 2); /* push f */
lua_rawgeti(L, 1, i); /* push t[i] */
lua_call(L, 1, 1); /* call f(t[i]) */
lua_rawseti(L, 1, i); /* t[i] = result */
}
return 0; /* no results */
}
写C函数技巧:字符串处理
C字符串和lua字符串差异挺大的。之间传送字符容易出问题。
lua的字符串不可修改,lua的字符串给C时,C别想着去修改字符串,也不要将其出栈。
C给lua字符串时,C要处理好缓冲区分配释放。
少量字符串
//等价于lua的连接符..
//适合少量字符串,类似table.concat
lua_concat(L, n) //连接(同时会出栈)栈顶的n个值,并将最终结果放到栈顶。
//格式化字符串的,类似sprintf,无需提供缓冲数组,lua自动管理,你尽管加的的来就是。适合少量字符串
//%%(表示字符 '%')、
//%s(用来格式化字符串)、
//%d(格式化整数)、
//%f(格式化Lua数字,即 double)和
//%c(接受一个数字并将其作为字符),
//不支持宽度和精度等选项
const char *lua_pushfstring (lua_State *L, const char *fmt, ...);
lua_pushfstring(L, "%%%d:%s:%f:%c",100,"100",100.0,124) //插入栈顶
lua_pushlstring(L, s+i, j-i+1); //将字符串s从i到j位置(包含i和j)的子串传递给lua
例子:字符串分割
//在lua中,
//split("hi,,there", ",")以,号分割字符串,并把子串保存在table中。
static int l_split (lua_State *L) {
const char *s = luaL_checkstring(L, 1);
const char *sep = luaL_checkstring(L, 2);
const char *e;
int i = 1;
lua_newtable(L); /* result */
/* repeat for each separator */
while ((e = strchr(s, *sep)) != NULL) {
lua_pushlstring(L, s, e-s); /* push substring */
lua_rawseti(L, -2, i++);
s = e + 1; /* skip separator */
}
/* push last substring */
lua_pushstring(L, s);
lua_rawseti(L, -2, i);
return 1; /* return the table */
}
大量字符串
luaL_Buffer,类似I/O buffer,满了才flush。
同时用到了前面讲到的栈算法来连接多个buffer。
/**********lstrlib.c string.upper实现**************/
static int str_upper (lua_State *L) {
size_t l;
size_t i;
luaL_Buffer b;
const char *s = luaL_checklstr(L, 1, &l);
luaL_buffinit(L, &b); //初始化b,L状态的拷贝
for (i=0; i<l; i++)
luaL_putchar(&b, toupper((unsigned char)(s[i]))); //单个字符
luaL_pushresult(&b); //刷新buffer并将最终字符串放到栈顶
return 1;
}
写C函数技巧:在C函数中保存状态
the registry:存储全局
C函数可以将非局部数据可以保存在lua的一个叫registry的表中,C代码可以自由使用,但Lua代码不能访问他。
registry 一直位于一个由LUA_REGISTRYINDEX定义的值所对应的假索引(pseudo-index)的位置。假索引意思就是可以通过访问栈这个位置来得到,但是他又不在栈中。但是注意,如果会改变栈的时候如lua_remove、lua_insert,不要用这个索引。
static const char Key = 'k'; //唯一key,用于解决registry可能存在的域冲突。
//保存一个值到registry中
lua_pushlightuserdata(L, (void *)&Key); //压栈一个C指针
lua_pushnumber(L, myNumber); //压栈一个myNumber
lua_settable(L, LUA_REGISTRYINDEX); //registry[&Key] = myNumber
//从registry中中获取一个值
lua_pushlightuserdata(L, (void *)&Key); //压栈一个C指针
lua_gettable(L, LUA_REGISTRYINDEX); //压榨registry[&Key]
myNumber = lua_tonumber(L, -1); //转成number
reference:解决key冲突
所有C库共享registry,这样可能引起域冲突。
方法一:借鉴lua中的{}作为唯一key,C中可以用一个static变量地址来充当registry,C链接器会保证这个地址是惟一的。
方法二:universal unique identifier(uuid),很多系统都有专门的程序来产生这种标示符(比如linux下的uuidgen)。一个uuid是一个由本机IP地址、时间戳、和一个随机内容组合起来的128位的数字(以16进制的方式书写,用来形成一个字符串)。保证每个uuid都是唯一的。
不要用数字作为key,lua的Reference系统已经占用了数字,用于提供给C一个唯一数值,这个值一般用来当做指针使用,方便C去引用lua的变量,使用场景如下。请记住,不要想着通过C指针来引用lua对象,这不是Lua的设计思想。
//想将栈中的值保存到registry中,但又没好的方法找唯一域的时候
int r = luaL_ref(L, LUA_REGISTRYINDEX); //将数据保存到registry中,
//栈顶弹出一个元素并保存到registry中,
//并返回指向它的数值
lua_rawgeti(L, LUA_REGISTRYINDEX, r); //registry表中获取r对应的值并压栈
luaL_unref(L, LUA_REGISTRYINDEX, r); //解除引用
//如果要保存到registry的数据是nil时,Reference是常量LUA_REFNIL
luaL_unref(L, LUA_REGISTRYINDEX, LUA_REFNIL); //无效
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_REFNIL); //有效,nil压栈
//Reference常量LUA_NOREF:一个表示任何非有效的reference的整数值
//用来判断reference是否有效。
upvalues:实现static
比如C端实现一个统计调用次数的函数给lua调用,常规的用static,最好的办法用lua的upvalue代替
//闭包函数
static int counter (lua_State *L){
//第一个upvalue的假索引,何为假索引见前面解释
double val = lua_tonumber(L, lua_upvalueindex(1));
lua_pushnumber(L, ++val); /* new value */
lua_pushvalue(L, -1); //拷贝一份压栈
lua_replace(L, lua_upvalueindex(1)); //用拷贝的那一份替换加索引的
return 1; //返回new value
}
int newCounter (lua_State *L) {
lua_pushnumber(L, 0);
lua_pushcclosure(L, &counter, 1); //1个upvalue
return 1;
}
五、Lua调用C类:UserDefined Types
一个userdata值提供了一个在Lua中没有预定义操作的raw内存区域。
把userdata看成一块内存。
Full Userdata
注册一个数组类型给Lua
原始方法
最基础原始的做法,见下面代码,有一个重大安全隐患,userdata是一块内存区域,必须要先确保类型匹配才不会导致内存越界操作。
------------在lua中的使用方式如下
a = array.new(1000)
print(a) --> userdata: 0x8064d48
print(array.size(a)) --> 1000
for i=1,1000 do
array.set(a, i, 1/i)
end
print(array.get(a, 10)) --> 0.1
typedef struct NumArray { //声明一个数组结构
int size;
double values[1]; //1是占位符,就是一个double
} NumArray;
//按照指定的大小分配一块内存,将对应的userdata放到栈内,并返回内存块的地址。
void *lua_newuserdata (lua_State *L, size_t size);
//a = array.new(1000)
static int newarray (lua_State *L) {
int n = luaL_checkint(L, 1); //luaL_checkint是用来检查整数的luaL_checknumber的变体
size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double); //有n个NumArray的数组的字节数
NumArray *a = (NumArray *)lua_newuserdata(L, nbytes); //分配内存,已经在栈顶。
a->size = n;
return 1;
}
//array.set(array, index, value)
static int setarray (lua_State *L) {
//重大漏洞隐患,没有安全防护,不知道内存地址是否有效
//加入在lua中array.set(io.stdin, 1, 0)。
//程序运行的结果可能导致内存core dump
NumArray *a = (NumArray *)lua_touserdata(L, 1);
int index = luaL_checkint(L, 2);
double value = luaL_checknumber(L, 3);
// lua中这样调用才会保存
// array.set(a, 11, 0) --> stdin:1: bad argument #1 to 'set' ('array' expected)
luaL_argcheck(L, a != NULL, 1, "`array' expected");
luaL_argcheck(L, 1 <= index && index <= a->size, 2, "index out of range");
a->values[index-1] = value;
return 0;
}
static int getarray (lua_State *L) {
NumArray *a = (NumArray *)lua_touserdata(L, 1);
int index = luaL_checkint(L, 2);
luaL_argcheck(L, a != NULL, 1, "'array' expected");
luaL_argcheck(L, 1 <= index && index <= a->size, 2,
"index out of range");
lua_pushnumber(L, a->values[index-1]);
return 1;
}
static int getsize (lua_State *L) {
NumArray *a = (NumArray *)lua_touserdata(L, 1);
luaL_argcheck(L, a != NULL, 1, "`array' expected");
lua_pushnumber(L, a->size);
return 1;
}
//这是注册库的代码
static const struct luaL_reg arraylib [] = {
{"new", newarray},
{"set", setarray},
{"get", getarray},
{"size", getsize},
{NULL, NULL}
};
int luaopen_array (lua_State *L) {
luaL_openlib(L, "array", arraylib, 0);
return 1;
}
在Linux中,一个有100K元素的数组大概占用800KB的内存,同样的条件由Lua 表实现的数组需要1.5MB的内存。
改进1:修补userdata内存漏洞
解决上面的安全漏洞隐患。解决思路就是为userdata设置一个metatable,通过检查metatable来确定userdata是否是正确的userdata
array.get(io.stdin, 10) -- error: bad argument #1 to 'getarray' ('array' expected)
//创建一个新表(将用作metatable),将新表放到栈顶并建立表和registry中类型名的联系。
//这个关联是双向的:使用类型名作为表的key;同时使用表作为类型名的key
int luaL_newmetatable (lua_State *L, const char *tname);
//获取registry中的tname对应的metatable
void luaL_getmetatable (lua_State *L, const char *tname);
//检查在栈中指定位置的对象是否为带有给定名字的metatable的userdata,错误返回NULL,成功返回userata地址
void *luaL_checkudata (lua_State *L, int index, const char *tname);
//注册库,上面已写。
int luaopen_array (lua_State *L) {
//名为LuaBook.array的metable压栈,保存在registry中,类型名做索引
luaL_newmetatable(L, "LuaBook.array");
luaL_openlib(L, "array", arraylib, 0);
return 1;
}
static int newarray (lua_State *L) {
int n = luaL_checkint(L, 1);
size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
luaL_getmetatable(L, "LuaBook.array");
lua_setmetatable(L, -2); //为userdata设置metatable
a->size = n;
return 1; /* new userdatum is already on the stack */
}
static NumArray *checkarray (lua_State *L) {
void *ud = luaL_checkudata(L, 1, "LuaBook.array"); //检验userdata类型
luaL_argcheck(L, ud != NULL, 1, "`array' expected");
return (NumArray *)ud;
}
static int getsize (lua_State *L) {
NumArray *a = checkarray(L);
lua_pushnumber(L, a->size);
return 1;
}
static int setarray (lua_State *L) {
double newvalue = luaL_checknumber(L, 3);
*getelem(L) = newvalue;
return 0;
}
static int getarray (lua_State *L) {
lua_pushnumber(L, *getelem(L));
return 1;
}
//获取指定索引的元素
static double *getelem (lua_State *L) {
NumArray *a = checkarray(L);
int index = luaL_checkint(L, 2);
luaL_argcheck(L, 1 <= index && index <= a->size, 2,"index out of range");
/* return element address */
return &a->values[index - 1];
}
改进2:类的方式访问
要能够像下面这样,是对象的方式操作(:冒号)。
a = array.new(1000)
print(a:size()) --> 1000
a:set(10, 3.4)
print(a:get(10)) --> 3.4
--------------思路--------------
--在lua可以直接如下设置。在C中可以完全替代这些lua工作
local metaarray = getmetatable(array.new(1))
metaarray.__index = metaarray
metaarray.set = array.set
metaarray.get = array.get
metaarray.size = array.size
//getsize、getarray和setarray的实现前面已有
static const struct luaL_reg arraylib_f [] = {
{"new", newarray},
{NULL, NULL}
};
static const struct luaL_reg arraylib_m [] = {
{"__tostring", array2string},
{"set", setarray},
{"get", getarray},
{"size", getsize},
{NULL, NULL}
};
int luaopen_array (lua_State *L) {
luaL_newmetatable(L, "LuaBook.array");
lua_pushstring(L, "__index");
lua_pushvalue(L, -2); /* pushes the metatable */
lua_settable(L, -3); /* metatable.__index = metatable */
//表名NULL,注册的函数没有在table中,在lua中直接使用函数
luaL_openlib(L, NULL, arraylib_m, 0);
luaL_openlib(L, "array", arraylib_f, 0);
return 1;
}
int array2string (lua_State *L) {
NumArray *a = checkarray(L);
lua_pushfstring(L, "array(%d)", a->size);
return 1;
}
改进2:数组的方式访问
a = array.new(1000)
a[10] = 3.4 -- setarray
print(a[10]) -- getarray --> 3.4
--------------思路--------------
--在lua可以直接如下设置。在C中可以完全替代这些lua工作
local metaarray = getmetatable(newarray(1))
metaarray.__index = array.get
metaarray.__newindex = array.set
//代替以上的lua思路
int luaopen_array (lua_State *L) {
luaL_newmetatable(L, "LuaBook.array");
luaL_openlib(L, "array", arraylib, 0);
/* now the stack has the metatable at index 1 and
'array' at index 2 */
lua_pushstring(L, "__index");
lua_pushstring(L, "get");
lua_gettable(L, 2); /* get array.get */
lua_settable(L, 1); /* metatable.__index = array.get */
lua_pushstring(L, "__newindex");
lua_pushstring(L, "set");
lua_gettable(L, 2); /* get array.set */
lua_settable(L, 1); /* metatable.__newindex = array.set */
return 0;
}
Light Userdata
light userdata是一个表示C指针的值(也就是一个void 类型的值),没有metatable。由于它是一个值,我们不能创建它们,像数字一样,不需要垃圾回收管理。
full userdata,表示一块内存区域。
light userdata,void指针,可以指向任何类型,也就可以是light userdata指向full userdata。
//将一个light userdatum直接入栈
void lua_pushlightuserdata (lua_State *L, void *p);