Lua与C之间的调用

来源:互联网 发布:腾讯大数据分析平台 编辑:程序博客网 时间:2024/05/20 18:46

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语言之间的两大差异

  1. Lua使用垃圾收集,而C语言要求显示释放内存
  2. Lua使用动态类型,而C语言使用静态类型

示例

C API,实现C调用Lua,执行万能的HelloWorld

VS2010配置

VS2010中的解决方案资源管理器结构如图Lua_HelloWorld资源管理器结构,先用已有的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);

这种做法有两个缺点:

  1. 很难讲这种复杂的类型映射到其他语言中。Lua本身就要多语言适用(c++,java,c#…)
  2. 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,并附上错误信息。


  1. 少于400行代码 ↩
  2. 这个常量是由LUA_MINSTACK定义的 ↩
1 0
原创粉丝点击