• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

一篇个人感觉比较好的lua入门的文章

原作者: [db:作者] 来自: [db:来源] 收藏 邀请
原文转自www.cppprog.com,由三篇文章组成

Lua是一个嵌入式的脚本语言,它不仅可以单独使用还能与其它语言混合调用。
Lua与其它脚本语言相比,其突出优势在于:

1.  可扩展性。Lua的扩展性非常卓越,以至于很多人把Lua用作搭建领域语言的工具(注:比如游戏脚本)。Lua被设计为易于扩展的,可以通过Lua代码或者 C代码扩展,Lua的很多功能都是通过外部库来扩展的。Lua很容易与C/C++、java、fortran、Smalltalk、Ada,以及其他语言接口。

2.  简单。Lua本身简单,小巧;内容少但功能强大,这使得Lua易于学习,很容易实现一些小的应用。他的完全发布版(代码、手册以及某些平台的二进制文件)仅用一张软盘就可以装得下。

3.  高效率。Lua有很高的执行效率,统计表明Lua是目前平均效率最高的脚本语言。

4.  与平台无关。Lua几乎可以运行在所有我们听说过的系统上,如NextStep、OS/2、PlayStation II (Sony)、Mac OS-9、OS X、BeOS、MS-DOS、IBM mainframes、EPOC、PalmOS、MCF5206eLITE Evaluation Board、RISC OS,及所有的Windows和Unix。Lua不是通过使用条件编译实现平台无关,而是完全使用ANSI (ISO) C,这意味着只要你有ANSI C编译器你就可以编译并使用Lua。

要在C++中使用Lua非常简单,不管是GCC,VC还是C++Builder, 最简单的方法就是把Lua源码中除lua.c,luac.c和print.c以外的所有c文件与你的代码一起编译链接(或加入到工程中)即可。
当然,为了方便维护,最好还是先把Lua编译成库文件再加入工程。方法如下:

GCC

    直接在Lua所在目录下make [环境]

    这里的[环境]可以是:aix ansi bsd freebsd generic linux macosx mingw posix solaris

    如果你的环境不在这个列表中,你可以试试ansi或posix。

 

VC

    在命令行环境下进入Lua所在目录,执行etc\luavs.bat编译。

 

C++Builder

    请看我的Blog,如何在C++Builder里编译Lua

 

头文件

    因为Lua是用C语言写的,除非编译lua库时指定编译器强制以C++方式编译,否则在C++工程中应该这样包含lua头文件:

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

 

例一,简单运行Lua代码

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.   

7.  #include <iostream>

8.  #include <string>

9.  using namespace std;

10. 

11.int main()

12.{

13.    lua_State *L = lua_open();    //初始化lua

14.    luaL_openlibs(L);    //载入所有lua标准库

15. 

16.    string s;

17.    while(getline(cin,s))    //从cin中读入一行到s

18.    {

19.        //载入s里的lua代码后执行

20.        bool err = luaL_loadbuffer(L, s.c_str(), s.length(),

21.                    "line") || lua_pcall(L, 0, 0, 0);

22.        if(err)

23.        {

24.            //如果错误,显示

25.            cerr << lua_tostring(L, -1);

26.            //弹出错误信息所在的最上层栈

27.            lua_pop(L, 1);

28.        }

29.    }

30. 

31.    lua_close(L);//关闭

32.    return 0;

33.}


    这已经是一个功能完备的交互方式Lua解释器了。

    输入print "hello world"

    输出hello world

    输入for i=1,10 do print(i) end

    输出从1到10


    要调用Lua,首先要使用lua_open(对于5.0以后版本的Lua,建议使用luaL_newstate代替)产生一个lua_State,在使用完后调用lua_close关闭。
    所有Lua与C之间交换的数据都是通过Lua中的栈来中转的。
    在本例中:
        luaL_loadbuffer的功能是载入并编译内存中的一段Lua代码,然后作为一个代码块(称为chunk)压入栈中,其中的最后一个参数作为代码块的名称用于调试。和它功能类似的还有luaL_loadfile(载入文件),luaL_loadstring(载入字符串,本例中也可用它代替luaL_loadbuffer)。它们有一个相同的前缀:luaL_,为了简化编程,Lua C API将库中的核心函数包装后作为辅助函数提供一些常用功能,它们的形式都是luaL_*,如这里的三个luaL_load*都调用了lua_load。
        lua_pcall从栈顶取得函数并执行,如果出错,则返回一个非0值并把错误信息压入栈顶。关于它的更详细信息会在“例三,在C++中调用Lua子函数”中介绍。
        如果宿主程序检测到错误,就用lua_tostring从栈顶取得错误信息转成字符串输出,然后弹出这个错误信息。
    lua_tostring里的第二个参数指定要操作的数据处于栈的哪个位置,因为所有的数据只能通过栈来交换,所以很多Lua的C API都会要求指定栈的位置。1表示在栈中的第一个元素(也就是第一个被压入栈的),下一个索引是2,以此类推。我们也可以用栈顶作为参照来存取元素,使用负数:-1指出栈顶元素(也就是最后被压入的),-2指出它的前一个元素,以此类推。

例二,与Lua交换数据

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.   

7.  #include <iostream>

8.  #include <string>

9.  using namespace std;

10.    

11.int main()

12.{

13.    //Lua示例代码

14.    char *szLua_code =

15.        "r = string.gsub(c_Str, c_Mode, c_Tag) --宿主给的变量 "

16.        "u = string.upper(r)";

17.    //Lua的字符串模式

18.    char *szMode = "(%w+)%s*=%s*(%w+)";

19.    //要处理的字符串

20.    char *szStr = "key1 = value1 key2 = value2";

21.    //目标字符串模式

22.    char *szTag = "<%1>%2</%1>";

23. 

24.    lua_State *L = luaL_newstate();

25.    luaL_openlibs(L);

26. 

27.    //把一个数据送给Lua

28.    lua_pushstring(L, szMode);

29.    lua_setglobal(L, "c_Mode");

30.    lua_pushstring(L, szTag);

31.    lua_setglobal(L, "c_Tag");

32.    lua_pushstring(L, szStr);

33.    lua_setglobal(L, "c_Str");

34. 

35.    //执行

36.    bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),

37.                "demo") || lua_pcall(L, 0, 0, 0);

38.    if(err)

39.    {

40.        //如果错误,显示

41.        cerr << lua_tostring(L, -1);

42.        //弹出栈顶的这个错误信息

43.        lua_pop(L, 1);

44.    }

45.    else

46.    {

47.        //Lua执行后取得全局变量的值

48.        lua_getglobal(L, "r");

49.        cout << "r = " << lua_tostring(L,-1) << endl;

50.        lua_pop(L, 1);

51.        

52.        lua_getglobal(L, "u");

53.        cout << "u = " << lua_tostring(L,-1) << endl;    

54.        lua_pop(L, 1);

55.    }

56.    lua_close(L);

57.    return 0;

58.}


    这段代码把字符串中的key=value字符串全部转换成XML格式<key>value</key>
    在这个例子中,C++程序通过调用lua_pushstring把C字符串压入栈顶,lua_setglobal的作用是把栈顶的数据传到Lua环境中作为全局变量。
    执行代码完成后,使用lua_getglobal从Lua环境中取得全局变量压入栈顶,然后使用lua_tostring把栈顶的数据转成字符串。由于lua_tostring本身没有出栈功能,所以为了平衡(即调用前与调用后栈里的数据量不变),使用lua_pop弹出由lua_setglobal压入的数据。
    从上面的例子可以看出,C++和Lua之间一直围绕着栈在转,可见栈是极为重要的。有必要列出一些Lua C API中的主要栈操作先,它们的作用直接可以从函数名中看出。
压入元素到栈里

void lua_pushnil (lua_State *L);   

void lua_pushboolean (lua_State *L, int bool);

void lua_pushnumber (lua_State *L, double n);

void lua_pushlstring (lua_State *L, const char *s, size_t length);

void lua_pushstring (lua_State *L, const char *s);

void lua_pushcfunction (lua_State *L, lua_CFunction fn);


查询栈里的元素

lua_isnil (lua_State *L, int index);

lua_isboolean (lua_State *L, int index);

int lua_isnumber (lua_State *L, int index);

int lua_isstring (lua_State *L, int index);

int lua_isfunction (lua_State *L, int index);

int lua_istable (lua_State *L, int index);

int lua_isuserdata (lua_State *L, int index);

lua_islightuserdata (lua_State *L, int index);

lua_isthread (lua_State *L, int index);

 

转换栈里的元素

int                lua_toboolean (lua_State *L, int index);

double            lua_tonumber (lua_State *L, int index);

const char *    lua_tostring (lua_State *L, int index);

const char *    lua_tolstring (lua_State *L, int idx, size_t *len);

size_t            lua_strlen (lua_State *L, int index);

lua_CFunction   lua_tocfunction (lua_State *L, int idx);

void *          lua_touserdata (lua_State *L, int idx);

lua_State *     lua_tothread (lua_State *L, int idx);

 

Lua栈的维护

int  lua_gettop (lua_State *L);

    取得栈顶元素的索引,即栈中元素的个数

void lua_settop (lua_State *L, int index);

    设置栈顶索引,即设置栈中元素的个数,如果index<0,则从栈顶往下数,下同

void lua_pushvalue (lua_State *L, int index);

    把栈中指定索引的元素复制一份到栈顶

void lua_remove (lua_State *L, int index);

    删除指定索引的元素

void lua_insert (lua_State *L, int index);

    移动栈顶元素到指定索引的位置,栈中数目没有改变

void lua_replace (lua_State *L, int index);

    从栈顶弹出元素值并将其设置到指定索引位置,栈中的数目减一

int  lua_checkstack (lua_State *L, int extra);

    确保堆栈上至少有 extra 个空位。如果不能把堆栈扩展到相应的尺寸,函数返回 false 。这个函数永远不会缩小堆栈。

int  lua_pop(L,n)

    从栈顶弹出n个元素,它是一个lua_settop的包装:#define lua_pop(L,n)  lua_settop(L, -(n)-1)

 

表的操作
上面的列表中并没有lua_pushtable和lua_totable,那么怎样取得或设置Lua中的table数据呢?
在Lua中,table是一个很重要的数据类型,在table中不仅可以象C中的数据一样放一组数据,还可以象map一样以key=value的方式存放数据,如Lua代码中的:

tb = {"abc",12,true,x=10,y=20,z=30}

    前三个数据可以用tb[1]~tb[3]取得
    而后三个数据通过tb.x, tb.y, tb.z取得
尽管看起来很牛叉,不过剥开神奇的外衣,实际上Lua的table中,所有的数据都是以key=value的形式存放的,这句Lua代码也可以写成:

tb = {[1]="abc", [2]=12, [3] = true, ["x"]=10, ["y"]=20, ["z"]=30}

    它的形式就是[key]=value,所谓的tb.x只是tb["x"]的语法糖而已,如果愿意,也可以用tb["x"]取得这个数据10。
我们把上面的例子改成使用表的

1.  ...

2.  int main()

3.  {

4.      //Lua示例代码,使用table

5.      char *szLua_code =

6.          "x = {} --用于存放结果的table "

7.          "x[1],x[2] = string.gsub(c.Str, c.Mode, c.Tag) --x[1]里是结果,x[2]里是替换次数 "

8.          "x.u = string.upper(x[1])";

9.      //Lua的字符串模式

10.    char *szMode = "(%w+)%s*=%s*(%w+)";

11.    //要处理的字符串

12.    char *szStr = "key1 = value1 key2 = value2";

13.    //目标字符串模式

14.    char *szTag = "<%1>%2</%1>";

15. 

16.    lua_State *L = luaL_newstate();

17.    luaL_openlibs(L);

18. 

19.    //把一个tabele送给Lua

20.    lua_newtable(L);    //新建一个table并压入栈顶

21.    lua_pushstring(L, "Mode");// key

22.    lua_pushstring(L, szMode);// value

23.    //设置newtable[Mode]=szMode

24.    //由于上面两次压栈,现在table元素排在栈顶往下数第三的位置

25.    lua_settable(L, -3);

26.    //lua_settable会自己弹出上面压入的key和value

27. 

28.    lua_pushstring(L, "Tag");// key

29.    lua_pushstring(L, szTag);// value

30.    lua_settable(L, -3);    //设置newtable[Tag]=szTag

31. 

32.    lua_pushstring(L, "Str");// key

33.    lua_pushstring(L, szStr);// value

34.    lua_settable(L, -3);    //设置newtable[Str]=szStr

35. 

36.    lua_setglobal(L,"c"); //将栈顶元素(newtable)置为Lua中的全局变量c

37. 

38.    //执行

39.    bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),

40.                "demo") || lua_pcall(L, 0, 0, 0);

41.    if(err)

42.    {

43.        //如果错误,显示

44.        cerr << lua_tostring(L, -1);

45.        //弹出栈顶的这个错误信息

46.        lua_pop(L, 1);

47.    }

48.    else

49.    {

50.        //Lua执行后取得全局变量的值

51.        lua_getglobal(L, "x");

52. 

53.        //这个x应该是个table

54.        if(lua_istable(L,-1))

55.        {

56.            //取得x.u,即x["u"]

57.            lua_pushstring(L,"u");    //key

58.            //由于这次压栈,x处于栈顶第二位置

59.            lua_gettable(L,-2);

60.            //lua_gettable会弹出上面压入的key,然后把对应的value压入

61.            //取得数据,然后从栈中弹出这个value

62.            cout << "x.u = " << lua_tostring(L,-1) << endl;

63.            lua_pop(L, 1);

64.            

65.            //取得x[1]和x[2]

66.            for(int i=1; i<=2; i++)

67.            {

68.                //除了key是数字外,与上面的没什么区别

69.                lua_pushnumber(L,i);

70.                lua_gettable(L,-2);

71.                cout << "x[" << i <<"] = " << lua_tostring(L,-1) << endl;

72.                lua_pop(L, 1);

73.            }

74.        }

75. 

76.        //弹出栈顶的x

77.        lua_pop(L, 1);

78.    }

79.    lua_close(L);

80.    return 0;

81.}

本例中用到的新Lua C API是:

void lua_newtable (lua_State *L);

    新建一个空的table并压入栈顶。

void lua_settable (lua_State *L, int idx);

    lua_settable以table在栈中的索引作为参数,并将栈顶的key和value出栈,用这两个值修改table。

void lua_gettable (lua_State *L, int idx);

    lua_gettable以table在栈中的索引作为参数,弹出栈顶的元素作为key,返回与key对应的value并压入栈顶。

最后,Lua告别针对table提供了存取函数

void lua_rawgeti (lua_State *L, int idx, int n)

    取得table[n]并放到栈顶,上例中69-70行的lua_pushnumber(L,i);lua_gettable(L,-2);可以用lua_rawgeti(L,-1)代替。

lua_getfield (lua_State *L, int idx, const char *k)

    取得table.k并放到栈顶,上例中57-59行的lua_pushstring(L,"u");lua_gettable(L,-2);可以替换成lua_getfield(L,-1,"u")。

void lua_setfield (lua_State *L, int idx, const char *k)

    把栈顶的数据作为value放入table.k中,上例中的形如lua_pushstring(L, "key");lua_pushstring(L, value);lua_settable(L, -3);可以改成lua_pushstring(L, value);lua_setfield(L,-2,"key");的形式。

void lua_rawseti (lua_State *L, int idx, int n)

    把栈顶的数据作为value放入table[n]中

 

 

 

例三,在C++中调用Lua子函数

    在Lua中,函数和boolean一样也属于基本数据类型,所以同样可以使用lua_getglobal来取得函数,剩下的问题只是怎样执行它(函数元素)的问题了。

1.  ...

2.  int main()

3.  {

4.      //Lua示例代码,是一个函数

5.      char *szLua_code =

6.          "function gsub(Str, Mode, Tag)"

7.          "    a,b = string.gsub(Str, Mode, Tag) "

8.          "    c = string.upper(a) "

9.          "    return a,b,c --多个返回值 "

10.        "end";

11.    //Lua的字符串模式

12.    char *szMode = "(%w+)%s*=%s*(%w+)";

13.    //要处理的字符串

14.    char *szStr = "key1 = value1 key2 = value2";

15.    //目标字符串模式

16.    char *szTag = "<%1>%2</%1>";

17. 

18.    lua_State *L = luaL_newstate();

19.    luaL_openlibs(L);

20. 

21.    //执行

22.    bool err = luaL_loadbuffer(L, szLua_code, strlen(szLua_code),

23.                "demo") || lua_pcall(L, 0, 0, 0);

24.    if(err)

25.    {

26.        cerr << lua_tostring(L, -1);

27.        lua_pop(L, 1);

28.    }

29.    else

30.    {

31.        //Lua执行后取得全局变量的值

32.        lua_getglobal(L, "gsub");

33.        if(lua_isfunction(L,-1))    //确认一下是个函数

34.        {

35.            //依次放入三个参数

36.            lua_pushstring(L,szStr);

37.            lua_pushstring(L,szMode);

38.            lua_pushstring(L,szTag);

39.            //调用,我们有3个参数,要得到2个结果

40.            //你可能注意到gsub函数返回了3个,不过我们只要2个,这没有问题

41.            //没有使用错误处理回调,所以lua_pcall最后一个参数是0

42.            if(0 != lua_pcall(L, 3, 2, 0))

43.            {

44.                //如果错误,显示

45.                cerr << lua_tostring(L, -1);

46.                lua_pop(L, 1);                

47.            }

48.            else

49.            {

50.                //正确,得到两个结果,注意在栈里的顺序

51.                cout << "a = " << lua_tostring(L, -2) << endl;

52.                cout << "b = " << lua_tostring(L, -1) << endl;

53.                //弹出这两个结果

54.                lua_pop(L, 2);

55.            }

56.        }

57.        else

58.        {

59.            lua_pop(L,1);

60.        }

61.    }

62.    lua_close(L);

63.    return 0;

64.}


    调用Lua子函数使用的是lua_pcall函数,我们的所有例子中都有这个函数,它的说明如下:

        lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);

        作用:以保护模式调用一个函数。 
        要调用一个函数请遵循以下协议:首先,要调用的函数应该被压入堆栈;接着,把需要传递给这个函数的参数按正序压栈;这是指第一个参数首先压栈。最后调用lua_pcall;
        nargs 是你压入堆栈的参数个数。当函数调用完毕后,所有的参数以及函数本身都会出栈。而函数的返回值这时则被压入堆栈。返回值的个数将被调整为 nresults 个,除非 nresults 被设置成 LUA_MULTRET。在这种情况下,所有的返回值都被压入堆栈中。 Lua 会保证返回值都放入栈空间中。函数返回值将按正序压栈(第一个返回值首先压栈),因此在调用结束后,最后一个返回值将被放在栈顶。
        如果有错误发生的话, lua_pcall 会捕获它,然后把单一的值(错误信息)压入堆栈,然后返回错误码。lua_pcall 总是把函数本身和它的参数从栈上移除。 
        如果 errfunc 是 0 ,返回在栈顶的错误信息就和原始错误信息完全一致。否则,这个函数会被调用而参数就是错误信息。错误处理函数的返回值将被 lua_pcall 作为出错信息返回在堆栈上。

 

 

 

 

 

 

 

 

 

 

 

闭包和伪索引

http://www.cppprog.com/2009/0210/63.html

例四,在Lua代码中调用C++函数

    能Lua代码中调用C函数对Lua来说至关重要,让Lua能真正站到C这个巨人的肩膀上。
    要写一个能让Lua调用的C函数,就要符合lua_CFunction定义:typedef int (*lua_CFunction) (lua_State *L);
    当Lua调用C函数的时候,同样使用栈来交互。C函数从栈中获取她的参数,调用结束后将结果放到栈中,并返回放到栈中的结果个数。
    这儿有一个重要的概念:用来交互的栈不是全局栈,每一个函数都有他自己的私有栈。当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置。

1.  ...

2.  #include <complex> //复数

3.   

4.  //C函数,做复数计算,输入实部,虚部。输出绝对值和角度

5.  int calcComplex(lua_State *L)

6.  {

7.      //从栈中读入实部,虚部

8.      double r = luaL_checknumber(L,1);

9.      double i = luaL_checknumber(L,2);

10.    complex<double> c(r,i);

11.    //存入绝对值

12.    lua_pushnumber(L,abs(c));

13.    //存入角度

14.    lua_pushnumber(L,arg(c)*180.0/3.14159);

15.    return 2;//两个结果

16.}

17. 

18.int main()

19.{

20.    char *szLua_code =

21.        "v,a = CalcComplex(3,4) "

22.        "print(v,a)";

23. 

24.    lua_State *L = luaL_newstate();

25.    luaL_openlibs(L);

26.   

27.    //放入C函数

28.    lua_pushcfunction(L, calcComplex);

29.    lua_setglobal(L, "CalcComplex");

30.   

31.    //执行

32.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

33.    if(err)

34.    {

35.        cerr << lua_tostring(L, -1);

36.        lua_pop(L, 1);

37.    }

38. 

39.    lua_close(L);

40.    return 0;

41.}

    结果返回5 53.13...,和其它数据一样,给Lua代码提供C函数也是通过栈来操作的,因为lua_pushcfunction和lua_setglobal的 组合很常用,所以Lua提供了一个宏:
    #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))
    这两句代码也就可写成lua_register(L,"CalcComplex",calcComplex);
   

闭包(closure)

    在编写用于Lua的C函数时,我们可能需要一些类似于面向对象的能力,比如我们想在Lua中使用象这样的一个计数器类:

1.  struct CCounter{

2.      CCounter()

3.          :m_(0){}

4.      int count(){

5.          return ++i;

6.      }

7.  private:

8.      int m_;

9.  };

    这里如果我们仅仅使用lua_pushcfunction提供一个count函数已经不能满足要求(使用static? 不行,这样就不能同时使用多个计数器,并且如果程序中有多个Lua环境的话它也不能工作)。
    这时我们就需要一种机制让数据与某个函数关联,形成一个整体,这就是Lua中的闭包,而闭包里与函数关联的数据称为UpValue
    使用Lua闭包的方法是定义一个工厂函数,由它来指定UpValue的初值和对应的函数,如:

1.  ...

2.  //计算函数

3.  int count(lua_State *L)

4.  {

5.      //得到UpValue

6.      double m_ = lua_tonumber(L, lua_upvalueindex(1));

7.      //更改UpValue

8.      lua_pushnumber(L, ++m_);

9.      lua_replace(L, lua_upvalueindex(1));

10.    //返回结果(直接复制一份UpValue作为结果)

11.    lua_pushvalue(L, lua_upvalueindex(1));

12.    return 1; 

13.}

14.//工厂函数,把一个数字和count函数关联打包后返回闭包。

15.int newCount(lua_State *L)

16.{

17.    //计数器初值(即UpValue)

18.    lua_pushnumber(L,0);

19.    //放入计算函数,告诉它与这个函数相关联的数据个数

20.    lua_pushcclosure(L, count, 1);

21.    return 1;//一个结果,即函数体

22.}

23. 

24.int main()

25.{

26.    char *szLua_code =

27.        "c1 = NewCount() "

28.        "c2 = NewCount() "

29.        "for i=1,5 do print(c1()) end "

30.        "for i=1,5 do print(c2()) end";

31. 

32.    lua_State *L = luaL_newstate();

33.    luaL_openlibs(L);

34.   

35.    //放入C函数

36.    lua_register(L,"NewCount",newCount);

37.   

38.    //执行

39.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

40.    if(err)

41.    {

42.        cerr << lua_tostring(L, -1);

43.        lua_pop(L, 1);

44.    }

45. 

46.    lua_close(L);

47.    return 0;

48.}


    执行结果是:

    1

    2

    3

    4

    5

    1

    2

    3

    4

    5


    可以发现这两个计算器之间没有干扰,我们成功地在Lua中生成了两个“计数器类”。
    这里的关键函数是lua_pushcclosure,她的第二个参数是一个基本函数(例子中是count),第三个参数是UpValue的个数(例子中为 1)。在创建新的闭包之前,我们必须将关联数据的初始值入栈,在上面的例子中,我们将数字0作为初始值入栈。如预期的一样, lua_pushcclosure将新的闭包放到栈内,因此闭包作为newCounter的结果被返回。
    实际上,我们之前使用的lua_pushcfunction只是lua_pushcclosure的一个特例:没有UpValue的闭包。查看它的声明可 以知道它只是一个宏而已:
        #define lua_pushcfunction(L,f)    lua_pushcclosure(L, (f), 0)
    在count函数中,通过lua_upvalueindex(i)得到当前闭包的UpValue所在的索引位置,查看它的定义可以发现它只是一个简单的 宏:
        #define lua_upvalueindex(i)    (LUA_GLOBALSINDEX-(i))
    宏里的LUA_GLOBALSINDEX是一个伪索引,关于伪索引的知识请看下节

伪索引

    伪索引除了它对应的值不在栈中之外,其他都类似于栈中的索引。Lua C API中大部分接受索引作为参数的函数,也都可以接受假索引作为参数。
    伪索引被用来访问线程的环境,函数的环境,Lua注册表,还有C函数的UpValue。

  • 线程的环境(也就是放全局变量的地方)通常在伪索引 LUA_GLOBALSINDEX 处。
  • 正在运行的 C 函数的环境则放在伪索引 LUA_ENVIRONINDEX 之处。
  • LUA_REGISTRYINDEX则存放着Lua注册表。
  • C函数UpValue的存放位置见上节。

    这里要重点讲的是LUA_GLOBALSINDEX和LUA_REGISTRYINDEX,这两个伪索引处的数据是table类型的。
    LUA_GLOBALSINDEX位置上的table存放着所有的全局变量,比如这句
    lua_getfield(L, LUA_GLOBALSINDEX, varname);
    就是取得名为varname的全局变量,我们之前一直使用的lua_getglobal就是这样定义的:#define lua_getglobal(L,s)    lua_getfield(L, LUA_GLOBALSINDEX, (s))
    下面的代码利用LUA_GLOBALSINDEX得到所有的全局变量

1.  int main()

2.  {

3.      char *szLua_code =

4.          "a=10 "

5.          "b=\"hello\" "

6.          "c=true";

7.   

8.      lua_State *L = luaL_newstate();

9.      luaL_openlibs(L);

10.   

11.    //执行

12.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

13.    if(err)

14.    {

15.        cerr << lua_tostring(L, -1);

16.        lua_pop(L, 1);

17.    }

18.    else

19.    {

20.        //遍历LUA_GLOBALSINDEX所在的table得到

21.        lua_pushnil(L);

22.        while(0 != lua_next(L,LUA_GLOBALSINDEX))

23.        {

24.            // 'key' (在索引 -2 处) 和 'value' (在索引 -1 处)

25.            /*

26.            在遍历一张表的时候,不要直接对 key 调用 lua_tolstring ,

27.            除非你知道这个 key 一定是一个字符串。

28.            调用 lua_tolstring 有可能改变给定索引位置的值;

29.            这会对下一次调用 lua_next 造成影响。

30.            所以复制一个key到栈顶先

31.            */

32.            lua_pushvalue(L, -2);

33.            printf("%s - %s ",

34.                  lua_tostring(L, -1),    //key,刚才复制的

35.                  lua_typename(L, lua_type(L,-2))); //value,现在排在-2的位置了

36.            // 移除 'value' 和复制的key;保留源 'key' 做下一次叠代

37.            lua_pop(L, 2);

38.        }

39.    }

40.    lua_close(L);

41.    return 0;

42.}


    LUA_REGISTRYINDEX伪索引处也存放着一个table,它就是Lua注册表(registry)。这个注册表可以用来保存任何C代码想保存 的Lua值。
    加入到注册表里的数据相当于全局变量,不过只有C代码可以存取而Lua代码不能。因此用它来存储函数库(在下一节介绍)中的一些公共变量再好不过了。

 

   一个Lua库实际上是一个定义了一系列Lua函数的代码块,并将这些函数保存在适当的地方,通常作为table的域来保存。Lua的C库就是这样实现的。
    作为一个完整的库,我们还需要写一个函数来负责把库中的所有公共函数放到table里,然后注册到Lua全局变量里,就像luaopen_*做的一样。 Lua为这种需求提供了辅助函数luaL_register,它接受一个C函数的列表和他们对应的函数名,并且作为一个库在一个table中注册所有这些函数。
下例中注册了一个名为Files的库,定义了三个库函数:FindFirst,FindNext,FindClose。

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.   

7.  #include <iostream>

8.  #include <string>

9.  #include <windows.h>

10.using namespace std;

11. 

12.//函数库示例,Windows下查找文件功能

13.//输入:string路径名

14.//输出:userdata存放Handle(如果没找到,则是nil), string文件名

15.int findfirst( lua_State *L )

16.{

17.    WIN32_FIND_DATAA FindFileData;

18.    HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);

19.   

20.    if(INVALID_HANDLE_VALUE == hFind)

21.        lua_pushnil(L);

22.    else

23.        lua_pushlightuserdata(L, hFind);

24. 

25.    lua_pushstring(L, FindFileData.cFileName);

26.   

27.    return 2;

28.}

29. 

30.//输入:userdata:findfirst返回的Handle

31.//输出:string:文件名,如果没找到,则返回nil

32.int findnext( lua_State *L )

33.{

34.    WIN32_FIND_DATAA FindFileData;

35.    if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))

36.        lua_pushstring(L, FindFileData.cFileName);

37.    else

38.        lua_pushnil(L);

39.    return 1;

40.}

41. 

42.//输入:userdata:findfirst返回的Handle

43.//没有输出

44.int findclose( lua_State *L )

45.{

46.    ::FindClose(lua_touserdata(L,1));

47.    return 0;

48.}

49. 

50.//注册函数库

51.static const struct luaL_reg lrFiles [] = {

52.    {"FindFirst", findfirst},

53.    {"FindNext", findnext},

54.    {"FindClose", findclose},

55.    {NULL, NULL}    /* sentinel */

56.};

57.int luaopen_Files (lua_State *L) {

58.    luaL_register(L, "Files", lrFiles);

59.    return 1;

60.}

61. 

62.int main()

63.{

64.    char* szLua_code=

65.        "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "

66.        "if hFind then "

67.        "    repeat "

68.        "        print(sFile) "

69.        "        sFile = Files.FindNext(hFind) "

70.        "    until sFile==nil; "

71.        "    Files.FindClose(hFind) "

72.        "end";

73.    lua_State *L = luaL_newstate();

74.    luaL_openlibs(L);

75.    luaopen_Files(L);

76. 

77.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

78.    if(err)

79.    {

80.        cerr << lua_tostring(L, -1);

81.        lua_pop(L, 1);

82.    }

83.    lua_close(L);

84.    return 0;

85.}


    本例运行结果是显示出C盘根目录下所有的文件名。
    Lua官方建议把函数库写进动态链接库中(windows下.dll文件,linux下.so文件),这样就可以在Lua代码中使用loadlib函数动 态载入函数库
例如,我们把上面的例子改成动态链接库版本:
DLL代码,假定生成的文件名为fileslib.dll:

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.  #include <windows.h>

7.   

8.  BOOL APIENTRY DllMain( HMODULE hModule,

9.                         DWORD  ul_reason_for_call,

10.                       LPVOID lpReserved

11.                     )

12.{

13.    return TRUE;

14.}

15. 

16.//函数库示例,Windows下查找文件功能

17.//输入:string路径名

18.//输出:userdata存放Handle(如果没找到,则是nil), string文件名

19.int findfirst( lua_State *L )

20.{

21.    WIN32_FIND_DATAA FindFileData;

22.    HANDLE hFind = ::FindFirstFileA(luaL_checkstring(L,1), &FindFileData);

23.   

24.    if(INVALID_HANDLE_VALUE == hFind)

25.        lua_pushnil(L);

26.    else

27.        lua_pushlightuserdata(L, hFind);

28. 

29.    lua_pushstring(L, FindFileData.cFileName);

30.   

31.    return 2;

32.}

33. 

34.//输入:userdata:findfirst返回的Handle

35.//输出:string:文件名,如果没找到,则返回nil

36.int findnext( lua_State *L )

37.{

38.    WIN32_FIND_DATAA FindFileData;

39.    if(::FindNextFileA(lua_touserdata(L,1),&FindFileData))

40.        lua_pushstring(L, FindFileData.cFileName);

41.    else

42.        lua_pushnil(L);

43.    return 1;

44.}

45. 

46.//输入:userdata:findfirst返回的Handle

47.//没有输出

48.int findclose( lua_State *L )

49.{

50.    ::FindClose(lua_touserdata(L,1));

51.    return 0;

52.}

53. 

54.//注册函数库

55.static const struct luaL_reg lrFiles [] = {

56.    {"FindFirst", findfirst},

57.    {"FindNext", findnext},

58.    {"FindClose", findclose},

59.    {NULL, NULL}    /* sentinel */

60.};

61.//导出,注意原型为typedef int (*lua_CFunction) (lua_State *L);

62.extern "C"    __declspec(dllexport) int luaopen_Files (lua_State *L) {

63.    luaL_register(L, "Files", lrFiles);

64.    return 1;

65.}

Lua调用代码(或者直接使用Lua.exe调用,dll文件必须处于package.cpath指定的目录中,默认与执行文件在同一目录即可):

1.  extern "C" {

2.  #include "lua.h"

3.  #include "lualib.h"

4.  #include "lauxlib.h"

5.  }

6.   

7.  #include <iostream>

8.  #include <string>

9.  #include <windows.h>

10.using namespace std;

11. 

12.int main()

13.{

14.    char* szLua_code=

15.        "fileslib = package.loadlib('fileslib.dll', 'luaopen_Files') "

16.        "fileslib() "

17.        "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "

18.        "if hFind then "

19.        "    repeat "

20.        "        print(sFile) "

21.        "        sFile = Files.FindNext(hFind) "

22.        "    until sFile==nil; "

23.        "    Files.FindClose(hFind) "

24.        "end";

25.    lua_State *L = luaL_newstate();

26.    luaL_openlibs(L);

27. 

28.    bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

29.    if(err)

30.    {

31.        cerr << lua_tostring(L, -1);

32.        lua_pop(L, 1);

33.    }

34.    lua_close(L);

35.    return 0;

36.}


Lua代码里使用package.loadlib得到动态链接库中的luaopen_Files函数,然后调用它注册到Lua中,如果动态链接库中的导出 函数名称满足luaopen_<库名>的话,还可以使用require直接载入。
比如,如果把本例中的DLL代码里的导出函数名luaopen_Files改成luaopen_fileslib的话,Lua代码便可以改成:

1.  char* szLua_code=

2.          "require('fileslib') "

3.          "hFind,sFile = Files.FindFirst('c:\\\\*.*'); "

4.          ...

例五,与Lua交换自定义数据

    由于Lua中的数据类型远不能满足C语言的需要,为此Lua提供了userdata,一个userdata提供了一个在Lua中没有预定义操作的raw内 存区域。
    在例四的函数库代码中我们已经使用过lightuserdata,它是userdata的一个特例:一个表示C指针的值(也就是一个void *类型的值)。
    下面的例子我们使用userdata来给Lua提供一个窗体类用于建立,显示窗体。为了简化窗体控制代码,在C函数中我们使用了C++Builder的 VCL库,所以下面的代码要在C++Builder下编译才能通过。当然,稍微修改一下也可以使用MFC,QT,wxWidget等来代替。

1.  //---------------------------------------------------------------------------

2.  #include <vcl.h>

3.  extern "C" {

4.  #include "lua.h"

5.  #include "lualib.h"

6.  #include "lauxlib.h"

7.  }

8.   

9.  #include <iostream>

10.#pragma hdrstop

11.//---------------------------------------------------------------------------

12.#pragma argsused

13. 

14.typedef TWinControl* PWinControl;

15.//创建窗体,输入父窗体(或nil),类型,标题

16.//输出创建后的窗体

17.int newCtrl(lua_State *L)

18.{

19.    //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)

20.    TWinControl *Parent = NULL;

21.    //从userdata中取得TWinControl*

22.    if(lua_isuserdata(L,1))

23.        Parent = *(PWinControl*)lua_touserdata(L,1);

24.    String Type = UpperCase(luaL_checkstring(L, 2));

25.    String Text = lua_tostring(L, 3);

26. 

27.    TWinControl *R = NULL;

28. 

29.    if(Type == "FORM")

30.    {

31.        R = new TForm(Application);

32.    }

33.    else if(Type == "BUTTON")

34.    {

35.        R = new TButton(Application);

36.    }

37.    else if(Type == "EDIT")

38.    {

39.        R = new TEdit(Application);

40.    }

41.    else

42.    {

43.        luaL_error(L, "unknow type!");

44.    }

45. 

46.    if(Parent)

47.        R->Parent = Parent;

48. 

49.    if(!Text.IsEmpty())

50.        ::SetWindowText(R->Handle, Text.c_str());

51. 

52.    //新建userdata,大小为sizeof(PWinControl),用于存放上面生成的窗体指针

53.    PWinControl* pCtrl = (PWinControl*)lua_newuserdata(L,sizeof(PWinControl));

54.    *pCtrl = R;

55.    return 1;

56.}

57. 

58.//显示窗体

59.int showCtrl(lua_State *L)

60.{

61.    //input: TWinControl*, for TForm, use ShowModal

62.    TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);

63.    TForm *fm = dynamic_cast<TForm*>(Ctrl);

64.    if(fm)

65.        fm->ShowModal();

66.    else

67.        Ctrl->Show();

68.    return 0;

69.}

70. 

71.//定位窗体,输入窗体,左,上,右,下

72.int posCtrl(lua_State *L)

73.{

74.    //input: TWinControl*, Left, Top, Right, Bottom

75.    TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);

76.    Ctrl->BoundsRect = TRect(

77.        luaL_checkint(L, 2),

78.        luaL_checkint(L, 3),

79.        luaL_checkint(L, 4),

80.        luaL_checkint(L, 5));

81. 

82.    return 0;

83.}

84. 

85.//删除窗体

86.int delCtrl(lua_State *L)

87.{

88.    //input: TWinControl*

89.    TWinControl* Ctrl = *(PWinControl*)lua_touserdata(L,1);

90.    delete Ctrl;

91.    return 0;

92.}

93. 

94.//把这些函数作为VCL函数库提供给Lua

95.static const struct luaL_reg lib_VCL [] = {

96.    {"new", newCtrl},

97.    {"del", delCtrl},

98.    {"pos", posCtrl},

99.    {"show", showCtrl},

100.       {NULL, NULL}

101.   };

102.    

103.   int luaopen_VCL (lua_State *L) {

104.       luaL_register(L, "VCL", lib_VCL);

105.       return 1;

106.   }

107.    

108.   int main(int argc, char* argv[])

109.   {

110.       char* szLua_code=

111.           "fm = VCL.new(nil,'Form','Lua Demo'); "    //新建主窗体fm

112.           "VCL.pos(fm, 200, 200, 500, 300); "        //定位

113.           "edt = VCL.new(fm, 'Edit', 'Hello World'); "  //在fm上建立一个编辑框edt

114.           "VCL.pos(edt, 5, 5, 280, 28); "

115.           "btn = VCL.new(fm, 'Button', 'Haha'); "    //在fm上建立一个按钮btn

116.           "VCL.pos(btn, 100, 40, 150, 63); "

117.           "VCL.show(edt); "

118.           "VCL.show(btn); "

119.           "VCL.show(fm); "                       //显示

120.           "VCL.del(fm);";                         //删除

121.    

122.       lua_State *L = luaL_newstate();

123.       luaL_openlibs(L);

124.       luaopen_VCL(L);

125.    

126.       bool err = luaL_loadstring(L, szLua_code) || lua_pcall(L, 0, 0, 0);

127.       if(err)

128.       {

129.           std::cerr << lua_tostring(L, -1);

130.           lua_pop(L, 1);

131.       }   

132.    

133.       lua_close(L);

134.       return 0;

135.   }

136.   //---------------------------------------------------------------------------

 

使用metatable提供面向对象调用方式

    上面的VCL代码库为Lua提供了GUI的支持,但是看那些Lua代码,还处于面向过程时期。如何能把VCL.show(edt)之类的代码改成edt: show()这样的形式呢?还是先看代码:

1.  //---------------------------------------------------------------------------

2.  #include <vcl.h>

3.  extern "C" {

4.  #include "lua.h"

5.  #include "lualib.h"

6.  #include "lauxlib.h"

7.  }

8.   

9.  #include <iostream>

10.#pragma hdrstop

11.//---------------------------------------------------------------------------

12.#pragma argsused

13. 

14.typedef TWinControl* PWinControl;

15.//创建窗体,输入父窗体(或nil),类型,标题

16.//输出创建后的窗体

17.int newCtrl(lua_State *L)

18.{

19.    //input:TWinControl *Parent, type(TForm,TButton,TEdit), text(optional)

20.    TWinControl *Parent = NULL;

21. 

22.    if(lua_isuserdata(L,1))

23.        Parent = *(PWinControl*)luaL_checkudata(L,1,"My_VCL");

24.    String Type = UpperCase(luaL_checkstring(L, 2));

25.    String Text = lua_tostring(L, 3);

26. 

27.    TWinControl *R = NULL;

28. 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap