Lua与C之间的调用

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
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Lua与C的相互调用Lua的一个重要特性,可以使得Lua获得更高的灵活性和扩展性。Lua提供了一系列的API函数,使得Lua与C之间的交互变得更加容易。下面我将介绍一下Lua与C相互调用的一些基本知识和方法: 1. Lua调用C函数 Lua调用C函数的方法很简单,只需要将C函数注册到Lua中即可。我们可以使用lua_register函数将C函数注册到Lua中,然后在Lua脚本中使用该函数即可。例如,下面的代码将一个名为add的C函数注册到Lua中: ``` int add(lua_State* L) { int a = luaL_checknumber(L, 1); int b = luaL_checknumber(L, 2); lua_pushnumber(L, a + b); return 1; } int luaopen_mylib(lua_State* L) { lua_register(L, "add", add); return 1; } ``` 在上面的代码中,我们定义了一个名为add的C函数,该函数接受两个整数参数,然后将它们相加并将结果压入Lua栈中。接着我们使用lua_register函数将该函数注册到Lua中,并将其命名为add。最后我们将该函数打包成一个Lua模块,并使用luaopen_mylib函数将其注册到Lua中。 在Lua脚本中,我们可以像下面这样使用add函数: ``` local mylib = require "mylib" print(mylib.add(1, 2)) -- 输出3 ``` 2. C调用Lua函数 C调用Lua函数的方法比较复杂,需要使用一系列的API函数。下面是一个简单的示例: ``` int call_lua_function(lua_State* L) { // 加载Lua脚本 luaL_dofile(L, "test.lua"); // 获取Lua全局变量test lua_getglobal(L, "test"); // 判断test是否为函数 if (!lua_isfunction(L, -1)) { printf("test is not a function\n"); return 0; } // 压入函数参数 lua_pushnumber(L, 1); lua_pushnumber(L, 2); // 调用函数 lua_call(L, 2, 1); // 获取函数返回值 int result = lua_tonumber(L, -1); printf("result = %d\n", result); return 0; } ``` 在上面的代码中,我们首先使用luaL_dofile函数加载了一个名为test.luaLua脚本,然后使用lua_getglobal函数获取了一个名为test的全局变量,接着使用lua_pushnumber函数压入了两个参数,最后使用lua_call函数调用了test函数,并将返回值压入了Lua栈中。我们可以使用lua_tonumber函数获取返回值并将其转换为C语言中的整数类型。 在Lua脚本中,我们可以这样定义test函数: ``` function test(a, b) return a + b end ``` 在这个示例中,我们定义了一个名为test的函数,该函数接受两个参数,然后将它们相加并返回结果。 这就是Lua与C相互调用的基本方法,通过这些方法,我们可以在Lua中使用C函数,也可以在C程序中调用Lua函数,从而实现更加灵活和高效的程序设计。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值