Lua与C之间的调用
序
周六听了公司大神讲了Lua和C/C++的绑定以及Lua虚拟机的实现过程。感到受益匪浅,怕自己过几日就忘掉,于是写这篇文章用来记录,也看看自己还能回忆起来多少。
C API
概述
Lua是一种嵌入式语言,即Lua不是一个单独运行的程序,而是可以链接到其他程序的库。Lua解释器使Lua程序可以在不同硬件环境下可以跑起来(x86,arm)
,这个解释器是一个简单的应用程序1,它依靠Lua库实现主要功能。与此同时,一个使用了Lua的程序可以在Lua环境中注册用C语言实现新的函数,由此可以向Lua添加某些Lua无法直接用Lua编写的功能。
以上两种形式,第一种形式中C语言有控制权,Lua是一个库,这种形式C代码是“应用程序代码”;第二种形式中,Lua有控制权,C语言是个库,C是“库代码”。应用程序和库代码都是用同样的API来与Lua通信,这就是C API
Lua和C语言通信主要通过虚拟栈,栈可以解决Lua和C语言之间的两大差异
- Lua使用垃圾收集,而C语言要求显示释放内存
- Lua使用动态类型,而C语言使用静态类型
示例
用C API,实现C调用Lua,执行万能的HelloWorld
VS2010配置
VS2010中的解决方案资源管理器结构如图,先用已有的Lua源代码生成
Lua.lib
,TestLua引入该库,就可以调用相应的API方法。
代码实现
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
int _tmain(int argc, _TCHAR* argv[])
{
lua_State *L = luaL_newstate(); //打开Lua,创建新的环境
luaL_openlibs(L); //打开所有的标准库
const char *buf = "print('Hello World')"; //Lua代码
luaL_dostring(L,buf); //相当于Lua代码中的dostring
lua_close(L); //关闭Lua状态
getc(stdin);
return 0;
}
执行改代码后在输出窗口会输出Hello World
栈
在Lua中,a[k]=v
表达式里k和v可以是Lua中的任意类型(table,number,string…)。要在C语言中实现上述表达式,因为C是固定参数类型,所以要为每个类型写一个settabel函数。(这里要是用C++实现就可以用动态类型参数实现了…)
上述问题可以用C联合(union)来解决。假设这种类型叫lua_Value,能够表示所有Lua类型。那么settabel声明为:
void lua_settable(lua_Value a, lua_Value k, lua_Value v);
这种做法有两个缺点:
- 很难讲这种复杂的类型映射到其他语言中。Lua本身就要多语言适用(c++,java,c#…)
- Lua的垃圾回收机制。如果该变量在C变量中,Lua引擎认为该table是垃圾文件,回收它。
Lua引擎为了解决上述问题,采取的方法是:
用抽象栈在Lua和C语言之间传递数据,Lua调用C API从栈中弹出数据使用。为了将C类型压入栈,需要为C的每种类型定义一个特定的函数,该方法远小于为了适用不同语言而定义settabel的数量, 也加强了语言的扩展性。另外,这个栈是Lua管理,垃圾收集器能确定C语言使用那些值。
注:Lua严格按LIFO(先出后进)规范来操作这个栈,当调用Lua时,Lua只会该变栈的顶部。C可以任意操作。
压栈
/*
** push functions (C -> stack)
*/
LUA_API void (lua_pushnil) (lua_State *L);
LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n);
LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n);
LUA_API void (lua_pushunsigned) (lua_State *L, lua_Unsigned n);
LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t l);
LUA_API const char *(lua_pushstring) (lua_State *L, const char *s);
LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt,
va_list argp);
LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...);
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
LUA_API void (lua_pushboolean) (lua_State *L, int b);
LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p);
LUA_API int (lua_pushthread) (lua_State *L);
以上是每个对应C类型的压入函数。
注:当Lua启动,或Lua调用C语言时,占中至少会有20个空闲的槽2
出栈
出栈是指Lua用C API调用栈中元素。API使用索引来引用栈中的元素。
C中实现Lua的调用代码:
LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum);
LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum);
LUA_API lua_Unsigned (lua_tounsignedx) (lua_State *L, int idx, int *isnum);
LUA_API int (lua_toboolean) (lua_State *L, int idx);
LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len);
LUA_API size_t (lua_rawlen) (lua_State *L, int idx);
LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx);
LUA_API void *(lua_touserdata) (lua_State *L, int idx);
LUA_API lua_State *(lua_tothread) (lua_State *L, int idx);
LUA_API const void *(lua_topointer) (lua_State *L, int idx);
为了演示以上函数的调用,以下代码实现了一个打印整个栈的内容的辅助函数:
static void stackDump(lua_State* L){
int i;
int top = lua_gettop(L);
for (i=1;i<=top;i++)
{
int t = lua_type(L,i);
switch(t){
case LUA_TSTRING:{
printf("'%s'",lua_tostring(L,i));
break;
}
case LUA_TBOOLEAN:{
printf(lua_toboolean(L,i)?"true":"false");
break;
}
case LUA_TNUMBER:{
printf("'%g'",lua_tonumber(L,i));
break;
}
default:{
printf("'%s'",lua_typename(L,i));
break;
}
}
printf(" ");
}
printf("\n");
}
以下代码展示了使用上述代码实现打印的例子:
lua_State *L = luaL_newstate(); //打开Lua,创建新的环境
lua_pushboolean(L,1);
lua_pushnumber(L,10);
lua_pushnil(L);
lua_pushstring(L,"Hello");
stackDump(L);/*ture '10' 'nil' 'hello'*/
lua_close(L); //关闭Lua状态
错误处理
如果发生错误有两种做法:
1. 设置一个“紧急”函数,让它不要把控制权返回给Lua。例如,调用longjmp转到之前setjmp所设置的位置。
2. 让代码在“保护模式”下运行。
Lua用的标准的错误处理方法。当一个C函数检测到一个错误时,它就应该调用lua_error。lua_error函数会清理Lua中所有需要清理的东西,然后跳转回发起执行的lua_pcall,并附上错误信息。