Lua笔记(8)——C API对table的操作

先看下面的C程序(hello.c):

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

int main(void)
{   
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    lua_getglobal(L, "io");
    lua_getfield(L, -1, "write");
    lua_pushstring(L, "Hello\n");

    lua_pcall(L, 1, 0, 0);
    return 0;
}

lua_getglobal函数定义如下:

void lua_getglobal (lua_State *L, const char *name);
Pushes onto the stack the value of the global name.

是把全局变量的值push到堆栈里。在这个程序中,即是把io这个table的值存到堆栈中。

lua_getfield函数定义如下:

void lua_getfield (lua_State *L, int index, const char *k);
Pushes onto the stack the value t[k], where t is the value at the given index. As in Lua, this function may trigger a metamethod for the "index" event

也即把堆栈中指定索引(indextable中的keyk的值push到堆栈。在程序中,即把io.write函数存到堆栈中。

接下来lua_pushstring是把io.write函数的参数push到堆栈,而lua_pcall即执行io.write函数。

执行结果:

[root@Fedora test]# ./hello
Hello

Lua笔记(7)——luaL_loadfile和luaL_dofile的区别

luaL_loadfile()会加载和编译Lua脚本,但不会运行。而luaL_dofile不仅运行编译后的脚本,运行结束后还会把脚本pop出栈。看下面这个例子:

一个简单的脚本(test.lua):

print "Hello World!"

首先看调用luaL_loadfile()的程序:

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

static void stackDump(lua_State *L)
{
    int i = 0;
    int type = 0;
    int top = lua_gettop(L);

    printf("There is %d elements in stack: ", top);
    for (i = 1; i <= top; i++)
    {
        type = lua_type(L, i);
        switch (type)
        {
            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("Element type is %s", lua_typename(L, type));
                break;
            }
        }
        printf(" ");
    }
    printf("\n");
    return;
}

static void bail(lua_State *L)
{
    fprintf(stderr, "\nFATAL ERROR:%s\n\n", lua_tostring(L, -1));
    exit(1);
}
int main(void)
{   
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    if (luaL_loadfile(L, "test.lua"))
    {
        bail(L);
    }

    stackDump(L);

    lua_close(L);
    return 0;
}

执行结果:

[root@Fedora test]# ./a
There is 1 elements in stack: Element type is function

可以看到,并没有打印“Hello World!”,而且栈里还有一个类型为function的元素。

接下来看调用luaL_dofile()的程序:

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

static void stackDump(lua_State *L)
{
    int i = 0;
    int type = 0;
    int top = lua_gettop(L);

    printf("There is %d elements in stack: ", top);
    for (i = 1; i <= top; i++)
    {
        type = lua_type(L, i);
        switch (type)
        {
            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("Element type is %s", lua_typename(L, type));
                break;
            }
        }
        printf(" ");
    }
    printf("\n");
    return;
}

static void bail(lua_State *L)
{
        fprintf(stderr, "\nFATAL ERROR:%s\n\n", lua_tostring(L, -1));
        exit(1);
}
int main(void)
{
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    if (luaL_dofile(L, "test.lua"))
    {
        bail(L);
    }

    stackDump(L);

    lua_close(L);
    return 0;
}

执行结果:

[root@Fedora test]# ./a
Hello World!
There is 0 elements in stack:

可以看到,不仅打印了“Hello World!”,而且栈也变成了空。

参考资料:
lual_dofile(); wont load script with C++ and Lua

Lua笔记(6)——栈(stack)

C程序和Lua库之间通过栈(stack)来进行数据交换,并且栈中的每个槽位(slot)都能存放任意的Lua数据类型值。栈如下图所示:

                            |________|    <--  (-1)
                            |________|    <--  (-2)
                            |________|
                            | ...... |
                            |________|    <--  (2)
                            |________|    <--  (1)

栈底以1为起始索引,而栈顶则以-1作为起始索引。lua_gettop()函数可以返回当前栈中的元素个数,同时也是栈顶元素的索引值。如果是空栈,则 lua_gettop()返回0

Lua C API的核心就集中在对栈的操作上:当想要运行某个Lua脚本时,需要调用luaL_dofile()函数。而想执行Lua脚本的某个函数时,则首先要把函数push进栈(lua_getglobal()),如果函数需要参数,则参数也要相应地进栈(lua_pushXX())。接下来执行函数(比如lua_pcall()函数)。当函数退出时,返回值同样push进栈,C程序就可以使用lua_toXX()函数获得结果。请看下面这个例子:

一个简单的Lua脚本(test.Lua):

function add (a, b)
    return (a+b)
end

C程序如下:

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

static void bail(lua_State *L)
{
    fprintf(stderr, "\nFATAL ERROR:%s\n\n", lua_tostring(L, -1));
    exit(1);
}
int main(void)
{   
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    if (luaL_dofile(L, "test.lua"))
    {
        bail(L);
    }

    lua_getglobal(L, "add");


    lua_pushnumber(L, 1);
    lua_pushnumber(L, 2);

    if (lua_pcall(L, 2, 1, 0))
    {
        bail(L);
    }

    printf("%g\n", lua_tonumber(L, -1));

    lua_close(L);
    return 0;
}

执行结果如下:

3

如何编译使用Lua库的C程序

编写使用LuaC程序后,除了需要链接Lua库以外,还需要链接libmlibdl。以下面程序为例(test.c):

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

int main(void)
{
    char buf[256] = {0};
    int error = 0;

    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    while (fgets(buf, sizeof(buf), stdin) != NULL)
    {
        error = luaL_loadstring(L, buf) || lua_pcall(L, 0, 0, 0);
        if (error)
        {
            fprintf(stderr, "Error is %s.\n", lua_tostring(L, -1));
            lua_pop(L, 1);
        }
    }
    lua_close(L);
    return 0;
}

编译:

[root@Fedora test]# gcc -g -o test test.c -llua -lm -ldl

执行:

[root@Fedora test]# ./test
print("hello")
hello
^C
[root@Fedora test]#

Lua笔记(5)——C API头文件简介

搭建Lua环境时,安装程序会把以下5个头文件拷贝到指定目录(默认为/usr/local/include/):

-rw-r--r--.  1 root root 14825 Jun  3 09:03 lua.h
-rw-r--r--.  1 root root 20581 May 20 13:39 luaconf.h
-rw-r--r--.  1 root root  8433 Oct 29  2014 lauxlib.h
-rw-r--r--.  1 root root  1173 Feb  6  2014 lualib.h
-rw-r--r--.  1 root root   191 Dec 22  2004 lua.hpp

先从最简单的lua.hpp来看:

// lua.hpp
// Lua header files for C++
// <<extern "C">> not supplied automatically because Lua also compiles as C++

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

可以看到lua.hpp只是包含了其它的几个头文件,供C++程序使用。

lua.h定义了Lua提供的最基本的功能:创建Lua环境,调用Lua函数,在Lua环境中读写全局变量等。lua.h定义的所有成员(包括函数,宏等)都是用lua_LUA_作为前缀。

lauxlib.h定义了Lua辅助库(auxiliary library)提供的的函数,这个文件里定义的成员都是以luaL_作为前缀。Lua辅助库在lua.h定义的基础API之上,又封装了一层API,以提供更好的通用性。Lua辅助库并不会访问Lua的内部,所有操作都是通过lua.h的基础API去操作。

luaconf.h会定义Lua环境的配置信息。

lualib.h定义了Lua语言标准库的名字,以及打开这些库的函数。

Lua笔记(4)——C API综述

Lua是一门嵌入式语言(embedded language),这就意味着Lua并不是一个独立(stand-alone)的包(package),而是一个可以供其它想使用Lua功能的程序链接的库(library)。实际上,Lua解释器程序lua(可执行文件)就是利用Lua库实现的一个小程序:读入文件或字符串,丢给Lua库去做处理。

Lua可以作为一个单独的库供其它程序使用以扩展其功能,因此可以被称之为“extension language”。同时,使用Lua的程序亦可以向Lua环境注册新的函数,这些函数用C(或其它语言)编写,提供一些不能由Lua直接实现的功能,所以Lua又是一门“extensible language”。

Lua作为extension language时,C代码作为控制,Lua作为库;而作为“extensible language”时,正好反过来,Lua代码作为控制,C作为库。CLua之间的接口称之为C API,它是一组供C访问Lua的函数。

C代码和Lua程序之间通过一个虚拟的栈(stack)来进行通信。几乎所有的C API调用都会访问栈上的数值,并且所有的C代码和Lua程序之间的数据交换都通过这个栈。另外,栈也可以用来保存一些临时数据。栈帮忙解决LuaC之间两个很重要的“impedance mismatch”问题:
a)Lua具有内存垃圾(garbage)回收机制,而C需要显示地释放;
b)Lua是动态类型(dynamic typing),而C是静态类型(static typing)。

Lua笔记(3)——table浅析

TableLua中一种重要的数据结构,可以将其视为关联数组(associate array),除了nil以外,数字,字符串,Lua语言中的其它值均可成为数组的keyLua也使用table来代表包(package)和对象(object)。比如io.read就可以理解为“io”模块的read函数。

TableLua中既不是“值(value)”,也不是“变量(variable)”,而是对象(object)——一个动态分配的对象。Table都是匿名的,程序只能通过“引用(reference)”(或称之为“指针”)来操作它。当程序中所有指向某个table的引用都无效后,Lua的垃圾回收程序会回收这个table的内存。程序没有办法声明(declare)一个table,而只能通过构造表达式(constructor expression)来创建一个table,就像这样:

a = {}

同全局变量一样,没初始化的table元素的值为空:

> a = {}
> print(a["x"])
nil

同样,把table元素的值设为nil就会删除这个元素。这并不是个巧合,其实在Lua中,全局变量就是存在一个table里。

使用table时,要注意table["name"]table.nametable[name]三者的区别。其中table["name"]table.name是一样的,tablekey都是"name"这个字符串。而table[name]key则是name变量的值。参考下面这个例子:

> name = "a"
> table["name"] = 10
> table[name] = 20
> print(table["name"])
10
> print(table.name)
10
> print(table[name])
20
> print(table["a"])
20

使用整数作为key,就可以让table变成通常意义上的数组(array)或列表(list)。需要额外注意的是,在 Lua中,使用1(不是0)来索引数组第一个元素,这个一定要注意!!

如果一个列表中间没有“洞”,即nil元素,则称之为序列(sequence),对于序列,可以使用长度运算符#获得序列的长度:

> a = {}
> a[1] = 10
> a[2] = 20
> print(#a)
2

Lua笔记(2)

Lua程序的源代码可以从官网下载,直接make就好。如果遇到“fatal error: readline/readline.h: No such file or directory”的编译错误,请参考这篇文章

如果想自己使用gdb研究代码,可以把src/Makefile中的CFLAGS一行修改如下:

CFLAGS= -g -Wall -Wextra -DLUA_COMPAT_5_2 $(SYSCFLAGS) $(MYCFLAGS)

源码编译完完会生成两个可执行文件:lualuaclua就是语言的解释器,而luac则是编译器,它会把lua程序转换成字节码。

当在命令行执行lua命令时:

[root@Fedora lua-5.3.1]# lua
Lua 5.3.1  Copyright (C) 1994-2015 Lua.org, PUC-Rio
>

在另一个窗口使用pstack命令查看lua进程:

[root@Fedora lua-5.3.1]# pstack 3368
#0  0x00007f8039c2a2d0 in __read_nocancel () from /lib64/libc.so.6
#1  0x00007f8039f2489d in rl_getc () from /lib64/libreadline.so.6
#2  0x00007f8039f25197 in rl_read_key () from /lib64/libreadline.so.6
#3  0x00007f8039f0ec6c in readline_internal_char () from /lib64/libreadline.so.6
#4  0x00007f8039f0f3e5 in readline () from /lib64/libreadline.so.6
#5  0x000000000040460f in pushline (L=0x233e018, firstline=1) at lua.c:310
#6  0x00000000004048b3 in loadline (L=0x233e018) at lua.c:377
#7  0x0000000000404a54 in doREPL (L=0x233e018) at lua.c:411
#8  0x00000000004051a6 in pmain (L=0x233e018) at lua.c:588
#9  0x000000000040a848 in luaD_precall (L=0x233e018, func=0x233e700, nresults=1) at ldo.c:337
#10 0x000000000040acec in luaD_call (L=0x233e018, func=0x233e700, nResults=1, allowyield=0) at ldo.c:421
#11 0x00000000004075cb in f_call (L=0x233e018, ud=0x7ffc9fae64e0) at lapi.c:919
#12 0x0000000000409ec5 in luaD_rawrunprotected (L=0x233e018, f=0x407594 <f_call>, ud=0x7ffc9fae64e0) at ldo.c:142
#13 0x000000000040b4a7 in luaD_pcall (L=0x233e018, func=0x407594 <f_call>, u=0x7ffc9fae64e0, old_top=16, ef=0) at ldo.c:644
#14 0x000000000040769a in lua_pcallk (L=0x233e018, nargs=2, nresults=1, errfunc=0, ctx=0, k=0x0) at lapi.c:945
#15 0x0000000000405273 in main (argc=1, argv=0x7ffc9fae6628) at lua.c:607

可以看到lua程序阻塞在doREPL函数,等待用户输入。

解决编译错误:“readline/readline.h: No such file or directory”

Fedora 22系统上编译Lua 5.3.1源代码时,遇到以下编译错误:

lua.c:80:31: fatal error: readline/readline.h: No such file or directory

解决办法是安装readline-devel package

[root@Fedora]# dnf install readline-devel

基于Debian平台,则使用如下命令:

sudo apt-get install libreadline-dev

参考资料:
‘readline/readline.h’ file not found

Lua笔记(1)

Lua有8种数据类型:nilbooleannumberstringfunctiontableuserdatathread

nil类型只有一个值:nilnil表示没有值,所有未定义的变量值都为nil。请看下面这个例子:

[root@Fedora ~]# lua
Lua 5.3.0  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> a
nil

boolean有两个值:truefalse。在Lua中,只有nilfalse会被当成“假”,包括0在内的其它任何值都被当成“真”:

> not not nil
false
> not not false
false
> not not 0
true