lua 和 c 交互

Q:什么是Lua的虚拟栈?

A:C与Lua之间通信关键内容在于一个虚拟的栈。几乎所有的调用都是对栈上的值进行操作,所有C与Lua之间的数据交换也都通过这个栈来完成。另外,你也可以使用栈来保存临时变量。
每一个与Lua通信的C函数都有其独有的虚拟栈,虚拟栈由Lua管理。
栈的使用解决了C和Lua之间两个不协调的问题:第一,Lua会自动进行垃圾收集,而C要求显式的分配存储单元,两者引起的矛盾。第二,Lua中的动态类型和C中的静态类型不一致引起的混乱。

Q:虚拟栈索引?

A:正索引的顺序为元素入栈的先后顺序,栈中的第一个元素(也就是第一个被入栈的元素)索引为1,第二个元素索引为2,以此类推。我们也可以使用负索引,以栈顶作为索引的参照来存取元素,栈顶元素的索引为-1,其前面的一个元素索引为-2,以此类推。正负索引各有其适用的场合,灵活应用,简化编码。

Q:第一个例子?

A:一个简单的Lua解释器。

#include <stdio.h>
#include <string.h>
/* 定义对Lua的基本操作的函数,其中定义的所有函数都有"lua_"前缀。
 * 例如调用Lua函数(如"lua_pcall()"),获取以及赋值Lua的全局变量,注册函数供Lua调用,等等。
 */
#include <lua.h>
/* 定义"auxiliary library",其中定义的所有函数都有"luaL_"前缀。
 * "auxiliary library"使用lua.h中提供的基本API实现了更高一层抽象方法的实现。
 * 所有的Lua标准库都使用了auxiliary library。
 */
#include <lauxlib.h>
/* 定义打开各种库的函数。
 */
#include <lualib.h>

int main(void)
{
    char buff[256] = {0};
    int error = 0;
    lua_State *L = luaL_newstate();    // 创建Lua状态机。
    luaL_openlibs(L);    // 打开Lua状态机"L"中的所有Lua标准库。

    while (fgets(buff, sizeof(buff), stdin) != NULL) {    // 获取用户输入。
        /* "luaL_loadbuffer()",编译用户输入为Lua的"chunk"并将其入栈。
         * "line"是"chunk"的名字,用于调试信息和错误消息。
         * "lua_pcall()",以保护模式运行"chunk"并将其从栈顶弹出。
         * 两个函数均是在成功时返回"LUA_OK"(实际的值是0),
         * 失败时返回错误码,并将错误信息入栈。
         */
        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);    // 关闭Lua状态机。

    return 0;
}
prompt> gcc main.c -llua -ldl -lm
prompt> ./a.out
print("Hello World!")
Hello World!

Q:如何将元素入栈与出栈?

A:Lua中的数据类型与C中的并不一一对应,对于不同的数据类型,需要不同的入栈函数。

// 将"b"作为一个布尔值入栈。
void lua_pushboolean(lua_State *L, int b)

/* 将C函数"fn"以及其在虚拟栈上关联的"n"个值作为"Closure"入栈。
 * "n"最大为255,第一个被关联的值首先入栈,栈顶是最后一个被关联的值,
 * 这些值会在函数调用成功后被出栈。
 */
void lua_pushcclosure(lua_State *L, lua_CFunction fn, int n)

// 将C函数"f"作为函数入栈。内部实际调用"lua_pushcclosure(L, f, 0)"。
void lua_pushcfunction(lua_State *L, lua_CFunction f)

/* 将一个被格式化后的字符串入栈。函数返回这个字符串的指针。
 * 与C语言中的"sprintf()"类似,其区别在于:
 * 1、不需要为结果分配空间。
 *    其结果是一个Lua字符串,由Lua来关心其内存分配(同时通过垃圾收集来释放内存)。
 * 2、"fmt"不支持符号、宽度、精度。只支持:
 *    "%%": 字符'%'。
 *    "%s": 带零终止符的字符串,没有长度限制。
 *    "%f": "lua_Number"(Lua中的浮点数类型)。
 *    "%L": "lua_Integer"(Lua中的整数类型)。
 *    "%p": 指针或是一个十六进制数。
 *    "%d": "int"。
 *    "%c": "char"。
 *    "%U": 用"long int"表示的UTF-8字。
 */
const char *lua_pushfstring(lua_State *L, const char *fmt, ...)

/* 将长度为"len",非字面形式的字符串"s"入栈。
 * Lua对这个字符串做一个内部副本(或是复用一个副本),
 * 因此"s"处的内存在函数返回后,可以释放掉或是立刻重用于其它用途。
 * 字符串内可以是任意二进制数据,包括'\0'。函数返回内部副本的指针。
 */
const char *lua_pushlstring(lua_State *L, const char *s, size_t len)

// 将字面形式的字符串"s"入栈,函数自动给出字符串的长度。返回内部副本的指针。
const char *lua_pushliteral(lua_State *L, const char *s)

/* 将以'\0'结尾的字符串"s"入栈。
 * Lua对这个字符串做一个内部副本(或是复用一个副本),
 * 因此"s"处的内存在函数返回后,可以释放掉或是立刻重用于其它用途。
 * 函数返回内部副本的指针。如果"s"为"NULL",将"nil"入栈并返回"NULL"。
 */
const char *lua_pushstring(lua_State *L, const char *s)

// 等价于"lua_pushfstring()"。不过是用"va_list"接收参数,而不是用可变数量的实际参数。
const char *lua_pushvfstring(lua_State *L, const char *fmt, va_list argp)

// 将全局环境入栈。
void lua_pushglobaltable(lua_State *L)

// 将值为"n"的整数入栈。
void lua_pushinteger(lua_State *L, lua_Integer n)

/* 将一个轻量用户数据"p"入栈。
 * 用户数据是保留在Lua中的C值。轻量用户数据表示一个指针"void*"。
 * 它像一个数值,你不需要专门创建它,它也没有独立的元表,
 * 而且也不会被收集(因为从来不需要创建)。只要表示的C地址相同,两个轻量用户数据就相等。
 */
void lua_pushlightuserdata(lua_State *L, void *p)

// 将空值入栈。
void lua_pushnil(lua_State *L)

// 将一个值为"n"的浮点数入栈。
void lua_pushnumber(lua_State *L, lua_Number n)

// "L"表示的线程入栈。如果这个线程是当前状态机的主线程的话,返回1。
int lua_pushthread(lua_State *L)

// 将虚拟栈上索引"index"处的元素的副本入栈。
void lua_pushvalue(lua_State *L, int index)

对应的出栈函数就很简单了,只有一个lua_pop()

// 从虚拟栈中弹出"n"个元素。
void lua_pop(lua_State *L, int n)

Q:如何检查虚拟栈中元素的类型?

A:

// 如果栈中索引"index"处的元素为"bool"类型,则返回1,否则返回0。
int lua_isboolean(lua_State *L, int index)

// 如果栈中索引"index"处的元素是一个C函数,则返回1,否则返回0。
int lua_iscfunction(lua_State *L, int index)

// 如果栈中索引"index"处的元素是一个C函数或是一个Lua函数,则返回1,否则返回0。
int lua_isfunction(lua_State *L, int index)

// 如果栈中索引"index"处的元素是一个整数,则返回1,否则返回0。
int lua_isinteger(lua_State *L, int index)

// 如果栈中索引"index"处的元素是一个轻量级的"userdata",则返回1,否则返回0。
int lua_islightuserdata(lua_State *L, int index)

// 如果栈中索引"index"处的元素是一个"nil",则返回1,否则返回0。
int lua_isnil(lua_State *L, int index

// 如果"index"是一个无效索引时,返回1,否则返回0。
int lua_isnone(lua_State *L, int index)

// 如果"index"是一个无效索引或者"index"处的元素是一个"nil",则返回1,否则返回0。
int lua_isnoneornil(lua_State *L, int index)

/* 如果栈中索引"index"处的元素是一个数值或者是一个可以转换为数值的字符串,
 * 则返回1,否则返回0。
 */
int lua_isnumber(lua_State *L, int index)

/* 如果栈中索引"index"处的元素是一个字符串或者是一个可以转换为字符串的数值,
 * 则返回1,否则返回0。
 */
int lua_isstring(lua_State *L, int index)

// 如果栈中索引"index"处的元素是一个"table",则返回1,否则返回0。
int lua_istable(lua_State *L, int index)

// 如果栈中索引"index"处的元素是一个线程,则返回1,否则返回0。
int lua_isthread(lua_State *L, int index)

// 如果栈中索引"index"处的元素是一个"userdata",则返回1,否则返回0。
int lua_isuserdata (lua_State *L, int index)

// 如果栈中的"coroutine"可以被挂起,则返回1,否则返回0。
int lua_isyieldable(lua_State *L)

/* 返回栈中索引"index"处元素的类型。这些类型在"lua.h"中定义,如下:
 * #define LUA_TNONE       (-1)    // 无效
 * #define LUA_TNIL        0    // "nil"
 * #define LUA_TBOOLEAN        1    // "bool"
 * #define LUA_TLIGHTUSERDATA  2    // 轻量级"userdata"
 * #define LUA_TNUMBER     3    // 数值
 * #define LUA_TSTRING     4    // 字符串
 * #define LUA_TTABLE      5    // "table"
 * #define LUA_TFUNCTION       6    // 函数
 * #define LUA_TUSERDATA       7    // "userdata"
 * #define LUA_TTHREAD     8    // 线程
 */
int lua_type(lua_State *L, int index)

// 返回"tp"表示的类型的名字。"tp"是"lua_type()"的返回值之一。
const char *lua_typename(lua_State *L, int tp)

Q:如何转换虚拟栈中元素的类型?

A:

// 将栈中"index"处的元素转换为C中的"bool"值返回。
int lua_toboolean(lua_State *L, int index)

// 将栈中"index"处的元素转换为一个C函数返回。指定的元素必须是一个C函数,否则返回"NULL"。
lua_CFunction lua_tocfunction(lua_State *L, int index)

/* 将栈中"index"处的元素转换为一个整数返回。
 * 指定的元素必须是一个整数或是一个可以被转换为整数的数字或字符串,否则返回0。
 * 如果"isnum"非"NULL","*isnum"会被赋值为操作是否成功的"bool"值。
 */
lua_Integer lua_tointegerx(lua_State *L, int index, int *isnum)

// 内部调用"lua_tointegerx(L, index, NULL)"。
lua_Integer lua_tointeger(lua_State *L, int index)

/* 将栈中"index"处的元素转换为一个C字符串并将其指针返回。
 * 如果"len"非"NULL","*len"将获得字符串的长度。
 * 指定元素必须是一个字符串或是一个数字,否则返回返回"NULL"。
 * 如果指定元素是一个数字,函数会将元素的类型转换为字符串。
 * 返回的字符串结尾包含'\0',而在字符串中允许包含多个'\0'。
 * 函数返回的字符串应立即转存,否则有可能被Lua垃圾回收器回收。
 */
const char *lua_tolstring(lua_State *L, int index, size_t *len)

/* 将栈中"index"处的元素转换为一个浮点数返回。
 * 指定的元素必须是一个数字或是一个可被转换为数字的字符串,否则返回0。
 * 如果"isnum"非"NULL","*isnum"会被赋值为操作是否成功的"bool"值。
 */
lua_Number lua_tonumberx(lua_State *L, int index, int *isnum)

// 内部调用"lua_tonumberx(L, index, NULL)"。
lua_Number lua_tonumber(lua_State *L, int index)

/* 将栈中"index"处的元素转换为一个C指针返回。
 * 指定的元素必须是一个"userdata","table",线程或是一个函数,否则返回"NULL"。
 */
const void *lua_topointer(lua_State *L, int index)

// 内部调用"lua_tolstring(L, index, NULL)"。
const char *lua_tostring(lua_State *L, int index)

/* 将栈中"index"处的元素转换为一个Lua线程返回。
 * 指定的元素必须是一个线程,否则返回"NULL"。
 */
lua_State *lua_tothread(lua_State *L, int index)

/* 栈中"index"处的元素如果是一个完全"userdata",则返回其内存地址的指针;
 * 如果是一个轻量级"userdata",则返回其存储的指针。
 */
void *lua_touserdata(lua_State *L, int index)

Q:一些维护虚拟栈的方法?

A:

/* int lua_gettop(lua_State *L)
 * 返回栈顶元素的索引。
 * 因为栈中元素的索引是从1开始编号的,所以函数的返回值相当于栈中元素的个数。
 * 返回值为0表示栈为空。
 */
lua_gettop(L);    // 返回栈中元素的个数。

/* void lua_settop(lua_State *L, int index)
 * 设置栈顶为索引"index"指向处。
 * 如果"index"比"lua_gettop()"的值大,那么多出的新元素将被赋值为"nil"。
 */
lua_settop(L, 0);    // 清空栈。

/* void lua_remove(lua_State *L, int index)
 * 移除栈中索引"index"处的元素,该元素之上的所有元素下移。
 */

/* void lua_insert(lua_State *L, int index)
 * 将栈顶元素移动到索引"index"处,索引"index"(含)之上的所有元素上移。
 */

/* void lua_replace(lua_State *L, int index)
 * 将栈顶元素移动到索引"index"处。(相当于覆盖了索引"index"处的元素)
 */

一个”dump”整个堆栈内容的例子,

#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

static void stackDump(lua_State *L)
{
    int i = 0;
    int top = lua_gettop(L);    // 获取栈中元素个数。
    for (i = 1; i <= top; ++i)    // 遍历栈中每一个元素。
    {
        int t = lua_type(L, i);    // 获取元素的类型。
        switch(t)
        {
            case LUA_TSTRING:    // strings
                printf("'%s'", lua_tostring(L, i));
                break;

            case LUA_TBOOLEAN:    // bool
                printf(lua_toboolean(L, i) ? "true" : "false");
                break;

            case LUA_TNUMBER:    // number
                printf("%g", lua_tonumber(L, i));    // %g,自动选择%e或%f表示数值。
                break;

            default:    // other values
                printf("%s", lua_typename(L, t));    // 将宏定义的类型码转换为类型名称。
                break;
        }
        printf("  ");    // put a separator
    }
    printf("\n");
}

int main(void)
{
    lua_State *L = luaL_newstate();    // 创建Lua状态机。
    // 向虚拟栈中压入值。
    lua_pushboolean(L, 1);    // true
    lua_pushnumber(L, 10);    // 10
    lua_pushnil(L);    // nil
    lua_pushstring(L, "hello");    // "hello"
    stackDump(L);    // true  10  nil  'hello'

    lua_pushvalue(L, -4);    // 将索引-4处的值的副本入栈。
    stackDump(L);    // true  10  nil  'hello'  true

    lua_replace(L, 3);    // 将栈顶元素移动到索引3处,并覆盖原先的元素。
    stackDump(L);    // true  10  true  'hello'

    lua_settop(L, 6);    // 将栈顶设置为索引6处,多出来的新元素被赋值为"nil"。
    stackDump(L);    // true  10  true  'hello'  nil  nil

    lua_remove(L, -3);    // 移除索引-3处的元素,其上所有元素下移。
    stackDump(L);    // true  10  true  nil  nil

    lua_settop(L, -5);    // 将栈顶设置为索引-5处。
    stackDump(L);    // true

    lua_close(L);    // 关闭Lua状态机。

    return 0;
}

附加:

1、Lua库没有定义任何全局变量,它所有的状态都保存在动态结构lua_State中,而且指向这个结构的指针作为所有Lua函数的一个参数。这样的实现方式使得Lua能够重入(reentrant),并且为在多线程中的使用中作好准备。
2、如果你是在C++的代码中使用Lua,别忘了,

extern "C" {
    #include <lua.h>
}

3、Lua中的字符串可以不以'\0'作为结束符。这样,字符串中可以包含任意的二进制(甚至是'\0'),字符串的长度由明确的长度指定。
4、在lua_pushlstring()lua_pushliteral()以及lua_pushstring()中,Lua不保存字符串(变量)指针。因此当这些函数返回时,你就可以修改你的字符串了。
5、对于入栈是否有栈空间的情况,你需要自己判断,别忘了现在你是一个C程序员。当Lua启动或者任何Lua调用C的时候,虚拟栈中至少有20个空间(在”lua.h”中LUA_MINSTACK定义),这对于一般情况下够用了,所以一般不用考虑。但有时候确实需要更多的栈空间(比如调用一个不定参数的函数),此时你需要使用lua_checkstack检查栈空间的情况。

/* 确保堆栈上至少有"n"个额外空位。如果不能把堆栈扩展到相应的尺寸,函数返回"false"。
 * 失败的原因包括将把栈扩展到比固定最大尺寸还大(至少是几千个元素)或分配内存失败。
 * 这个函数永远不会缩小堆栈,如果堆栈已经比需要的大了,那么就保持原样。
 */
int lua_checkstack(lua_State *L, int sz)

6、遍历一个”table”时,不要将lua_tolstring()作用在”key”上,这样会导致lua_next()无法正常运行。
7、在平常的编码中,对于执行失败时会返回0lua_to*()类别的函数,我们最好先使用lua_is*()类别的函数判断参数的类型,之后再使用lua_to*()类别的函数对参数进行转换;而对于执行失败时会返回NULLlua_to*()类别的函数,我们可以直接使用lua_to*()类别的函数直接对参数进行转换,判断函数的返回值非NULL与否,就能判断转换是否成功。
8、lua_pop()就是通过lua_settop()实现的(在”lua.h”中定义),
#define lua_pop(L,n) lua_settop(L, -(n)-1)
9、以下操作对于虚拟栈没有任何影响,

lua_settop(L, -1);    /* set top to its current value */
lua_insert(L, -1);    /* move top element to the top */
lua_replace(L, -1);    /* replace top element by the top element */
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • Lua栈 要理解Lua和C++交互,首先要理解Lua堆栈。简单来说,Lua和C/C++语言通信的主要方法是一个无处...
    陈HHH阅读 333评论 0 0
  • 1. 写在前面 很多时候我们都需要借助一些脚本语言来为我们实现一些动态的配置,那么就会涉及到如何让脚本语言跟原生语...
    杰嗒嗒的阿杰阅读 3,425评论 9 31
  • 第一篇 语言 第0章 序言 Lua仅让你用少量的代码解决关键问题。 Lua所提供的机制是C不擅长的:高级语言,动态...
    testfor阅读 2,659评论 1 7
  • 当在Lua和C之间交换数据时主要的问题是自动回收与手动回收内存管理的不一致。因此,Lua 用一个抽象的栈在Lua与...
    luffier阅读 2,642评论 0 3
  • lua语言的功能就不用过多的描述,详细的可以去找相关的lua书籍去看看!这里主要说的是c++和lua相互调用的情况...
    David_Longzy阅读 1,661评论 0 2