LUA学习笔记

 

LUA学习笔记

本文从Programming in Lua中提取

使用LUA的目的:

当你真正要在项目中使用LUA时会明白自己的选择:

为了程序的可扩展性和随意性,减少后期需求的变化进项目造成的影响.

 

本文只介绍一些很肤浅关于LUA的C环境构建,及一些简单的LUA函数应用,是自己使用LUA的总结和入门记录.

 

LUA将给程序带来的功能:

1.LUA可以当一个配置文件使用(读写LUA全局变量)

2.程序中调用LUA定义的函数(C函数调用lua)

3.LUA可以调用自定义的C函数 (lua调用C函数)

4.运行LUA文件的函数(调用LUA文件运行函数)

5.运行LUA函数的函数(调用LUA代码片断的函数)

以上是LUA的一些基本应用,也是一个构建LUA的C环境必须实现的基本功能,主要是C程序和LUA的几个交互.下面会一一介绍,对于LUA的构架还是很必要的

 

LUA的总体认识:

头文件lua.h定义了Lua提供的基础函数。其中包括创建一个新的Lua环境的函数(如lua_open),调用Lua函数(如lua_pcall)的函数,读取/写入Lua环境的全局变量的函数,注册可以被Lua代码调用的新函数的函数,等等。所有在lua.h中被定义的都有一个lua_前缀。

头文件lauxlib.h定义了辅助库(auxlib)提供的函数。同样,所有在其中定义的函数等都以luaL_打头(例如,luaL_loadbuffer)。辅助库利用lua.h中提供的基础函数提供了更高层次上的抽象;所有Lua标准库都使用了auxlib。

 

构建一个最简单的LUA运行环境:

////////////////////构建LUA的基本运行环境

#include <stdio.h>

///使用C调用(不使用的话会有问题)

extern "C" { 

#include "lua.h"

#include "lauxlib.h"

#include "lualib.h"

}

#include <string>

int main (void)

{

    char buff[256];

    int error;

    //LUA库的初始化

    lua_State *L = lua_open(); /* opens Lua */

    luaopen_base(L); /* opens the basic library */

    luaopen_table(L); /* opens the table library */

    luaopen_io(L); /* opens the I/O library */

    luaopen_string(L); /* opens the string lib. */

    luaopen_math(L); /* opens the math lib. */

    while (fgets(buff, sizeof(buff), stdin) != NULL)

    {

        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);/* pop error message from the stack */+

        }

    }

    lua_close(L);

    return 0;

}

 

LUA堆栈原理:

当在Lua和C之间交换数据时我们面临着两个问题:动态与静态类型系统的不匹配和自动与手动内存管理的不一致。

用一个抽象的栈在Lua与C之间交换值。栈中的每一条记录都可以保存任何Lua值。无论你何时想要从Lua请求一个值(比如一个全局变量的值),调用Lua,被请求的值将会被压入栈。无论你何时想要传递一个值给Lua,首先将这个值压入栈,然后调用Lua(这个值将被弹出)。我们仍然需要一个不同的函数将每种C类型压入栈和一个不同函数从栈上取值(译注:只是取出不是弹出),但是我们避免了组合式的爆炸(combinatorial explosion)。另外,因为栈是由Lua来管理的,垃圾回收器知道那个值正在被C使用。几乎所有的API函数都用到了栈。正如我们在第一个例子中所看到的,luaL_loadbuffer把它的结果留在了栈上(被编译的chunk或一条错误信息);lua_pcall从栈上获取要被调用的函数并把任何临时的错误信息放在这里。

当Lua在起始以及在Lua调用C的时候,栈上至少有20个空闲的记录(lua.h中的LUA_MINSTACK宏定义了这个常量)

 

 

LUA基本函数介绍:

压入栈函数:

void lua_pushnil (lua_State *L);

void lua_pushboolean (lua_State *L, int bool);

void lua_pushnumber (lua_State *L, double n);

//Lua中的字符串不是以零为结束符的;Lua从来不保持一个指向外部字符串的指针

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

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

//同样也有将C函数和userdata值压入栈的函数.

int lua_checkstack (lua_State *L, int sz);

 

查询元素

API用索引来访问栈中的元素。在栈中的第一个元素(也就是第一个被压入栈的)有索引1,下一个有索引2,以此类推。

API提供了一套lua_is*函数来检查一个元素是否是一个指定的类型

int lua_is... (lua_State *L, int index);

如:lua_isnumber,lua_isstring,lua_istable

还有一个lua_type函数,它返回栈中元素的类型。(lua_is*中的有些函数实际上是用了这个函数定义的宏)在lua.h头文件中,每种类型都被定义为一个常量:LUA_TNIL、LUA_TBOOLEAN、LUA_TNUMBER、LUA_TSTRING、LUA_TTABLE、LUA_TFUNCTION、LUA_TUSERDATA以及LUA_TTHREAD。这个函数主要被用在与一个switch语句联合使用。当我们需要真正的检查字符串和数字类型时它也是有用的.

 

 

为了从栈中获得值,这里有lua_to*函数:

int lua_toboolean (lua_State *L, int index);

double lua_tonumber (lua_State *L, int index);

//Lua_tostring函数返回一个指向字符串的内部拷贝的指针

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

size_t lua_strlen (lua_State *L, int index);

 

其他堆栈操作:

除开上面所提及的C与堆栈交换值的函数外,API也提供了下列函数来完成通常的堆栈维护工作:

    //返回堆栈中的元素个数,它也是栈顶元素的索引

int lua_gettop (lua_State *L);

//lua_settop设置栈顶(也就是堆栈中的元素个数)为一个指定的值,lua_settop(L,0)清空堆栈

void lua_settop (lua_State *L, int index);

//压入堆栈上指定索引的一个抟贝到栈顶

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);

 

//测试代码,返回lua的类型

static void stackDump (lua_State *L)

{

    int i;

    int top = lua_gettop(L);

    for (i = 1; i <= top; i++)

 { /* repeat for each level */

        int t = lua_type(L, i);

        switch (t)

{

case LUA_TSTRING: /* strings */

    printf("`%s'", lua_tostring(L, i));

    break;

case LUA_TBOOLEAN: /* booleans */

    printf(lua_toboolean(L, i) ? "true" : "false");

    break;

case LUA_TNUMBER: /* numbers */

    printf("%g", lua_tonumber(L, i));

    break;

default: /* other values */

    printf("%s", lua_typename(L, t));

    break;

}

        printf(" "); /* put a separator */

    }

    printf("\n"); /* end the listing */

}

 

int main (void)

{

    lua_State *L = lua_open();

    lua_pushboolean(L, 1); lua_pushnumber(L, 10);

    lua_pushnil(L); lua_pushstring(L, "hello");

    stackDump(L);

    /* true 10 nil `hello' */

    lua_pushvalue(L, -4); stackDump(L);

    /* true 10 nil `hello' true */

    lua_replace(L, 3); stackDump(L);

    /* true 10 true `hello' */

    lua_settop(L, 6); stackDump(L);

    /* true 10 true `hello' nil nil */

    lua_remove(L, -3); stackDump(L);

    /* true 10 true nil nil */

    lua_settop(L, -5); stackDump(L);

    /* true */

lua_close(L);

return 0;

}

 

错误处理

Lua中的所有结构都是动态的:它们按需增长,最终当可能时又会缩减。意味着内存分配失败的可能性在Lua中是普遍的。几乎任意操作都会面对这种意外。Lua的API中用异常发出这些错误而不是为每步操作产生错误码。这意味着所有的API函数可能抛出一个错误(也就是调用longjmp)来代替返回。

你可以使用lua_atpanic函数设置你自己的panic函数

不是所有的API函数都会抛出异常,lua_open、lua_close、lua_pcall和lua_load都是安全的

 

 

 

 

 

 

 

 

下面将介绍LUA中真正会常用到的几个方法(LUA将给程序带来的功能所提到的)

1.LUA可以当一个配置文件使用(读写LUA全局变量)

lua配置文件:

width=100

height=200

 

读取配置文件程序:

#include <lua.h>

#include <lauxlib.h>

#include <lualib.h>

void load (char *filename, int *width, int *height)

{

     lua_State *L = lua_open();

     luaopen_base(L);

//   luaopen_io(L);

     luaopen_string(L);

     luaopen_math(L);

     if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))

     {//读取LUA的配置文件

         printf("cannot run configuration file: %s", lua_tostring(L, -1));

     }

     //得到相应的配置文件内容

     lua_getglobal(L, "width");

     lua_getglobal(L, "height");

     if (!lua_isnumber(L, -2))

     {

         printf("`width' should be a number\n");

     }

     if (!lua_isnumber(L, -1))

     {

         printf("`height' should be a number\n");

     }

     *width = (int)lua_tonumber(L, -2);

     *height = (int)lua_tonumber(L, -1);

     lua_close(L);

}

 

 

 

 

 

 

 

 

 

 

 

 

读取配置文件中的TABLE数据

 

lua 配置内容 :

background = {r=0.30, g=0.10, b=0}

 

程序:

int GetField(lua_State* L, const char* key)

{

     int result;

     lua_pushstring(L, key);          //把要得到的值压入

     lua_gettable(L, -2);             //他接受table在栈中的位置为参数,将对应key值出栈

     if (!lua_isnumber(L, -1))       

     {

         printf("invalid component in background color");

     }

     result = (int)lua_tonumber(L, -1);

     lua_pop(L, 1); /* remove number */

     return result;

}

 

//从lua中得到table的数据

//RED = {r=255, g=0, b=0}

//GRE = {r=0, g=255, b=0}

//BLU = {r=0, g=0, b=255}

void GetTableInfo(char *filename, const char* szTableName)

{

     lua_State *L = lua_open();

     luaopen_base(L);

     luaopen_string(L);

     luaopen_math(L);

     if (luaL_loadfile(L, filename) || lua_pcall(L, 0, 0, 0))

     {

         printf("cannot run configuration file: %s", lua_tostring(L, -1));

     }

     lua_getglobal(L, szTableName);                     //得到table的全局变量

     if (!lua_istable(L, -1))

     {

         printf("`width' should be a number\n");

     }

     int Red = GetField(L, "r");               //得到Table中的r值

     int Gre = GetField(L, "g");

     int Blu = GetField(L, "b");

     printf("%d,%d,%d",Red, Gre, Blu);

     lua_close(L);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

2.程序中调用LUA定义的函数(C函数调用lua)

程序中调用LUA定义的函数

使用API调用函数的方法是很简单的:

首先,将被调用的函数入栈;

第二,依次将所有参数入栈;

第三,使用lua_pcall调用函数;

最后,从栈中获取函数执行返回的结果。

 

lua函数

function f (x, y)

return (x^2 * math.sin(y))/(1 - x)

end

 

//在C中对于给定的x,y计算z=f(x,y)的值

/* call a function `f' defined in Lua */

double f (double x, double y)

{

double z;

/* push functions and arguments */

lua_getglobal(L, "f"); /* function to be called */

lua_pushnumber(L, x); /* push 1st argument */

lua_pushnumber(L, y); /* push 2nd argument */

/* do the call (2 arguments, 1 result) */

if (lua_pcall(L, 2, 1, 0) != 0) //。第四个参数可以指定一个错误处理函数

error(L, "error running function `f': %s", lua_tostring(L, -1));

/* retrieve result */

if (!lua_isnumber(L, -1))

error(L, "function `f' must return a number");

z = lua_tonumber(L, -1);

lua_pop(L, 1); /* pop returned value */

return z;                                                                   

}

 

在将结果入栈之前,lua_pcall会将栈内的函数和参数移除。如果函数返回多个结果,第一个结果被第一个入栈,因此如果有n个返回结果,第一个返回结果在栈中的位置为-n,最后一个返回结果在栈中的位置为-1。

lua_pcall返回三个LUA_ERRRUN LUA_ERRMEM LUA_ERRER

 

我们的封装后的函数(call_va)接受被调用的函数明作为第一个参数,第二参数是一个描述参数和结果类型的字符串,最后是一个保存返回结果的变量指针的列表。使用这个函数,我们可以将前面的例子改写为:

call_va("f", "dd>d", x, y, &z); 与lua_pcall有相同的意思

字符串 "dd>d" 表示函数有两个double类型的参数,一个double类型的返回结果。我们使用字母 'd' 表示double;'i' 表示integer,'s' 表示strings;'>' 作为参数和结果的分隔符。如果函数没有返回结果,'>' 是可选的。

 

void call_va (lua_State *L, const char *func, const char *sig, ...)

{

va_list vl;

int iParamCnt = 0;//参数个数

int iRetCnt = 0;//返回值什数

va_start(vl, sig);

lua_getglobal(L, func); /* get function */

/* push arguments */

bool bRet = false;

while (*sig)

{ /* push arguments */

     switch (*sig)

     {

     case 'd': /* double argument */

         lua_pushnumber(L, va_arg(vl, double));

         break;

     case 'i': /* int argument */

         lua_pushnumber(L, va_arg(vl, int));

         break;

     case 's': /* string argument */

         lua_pushstring(L, va_arg(vl, char *));

         break;

     case '>':

         iParamCnt = iParamCnt - 1;

         bRet = true;

         break;

     default:

         printf("invalid option (%c)", *(sig - 1));

     }

     iParamCnt = iParamCnt + 1;

     *sig++;

     if (bRet)

     {

         break;

    }

     luaL_checkstack(L, 1, "too many arguments");

}/* endwhile:*/

/* do the call */

iRetCnt = strlen(sig); /* number of expected results */

if (lua_pcall(L, iParamCnt, iRetCnt, 0) != 0) /* do the call */

{

     printf("error running function `%s': %s", func, lua_tostring(L, -1));

}

/* retrieve results */

iRetCnt =  - iRetCnt; /* stack index of first result */

while (*sig)

{ /* get results */

     switch (*sig)

     {

     case 'd': /* double result */

         {      

             if (!lua_isnumber(L, iRetCnt))

             {

                 printf("wrong result type");

             }

             *va_arg(vl, double *) = lua_tonumber(L, iRetCnt);

         }

         break;

     case 'i': /* int result */

         if (!lua_isnumber(L, iRetCnt))

         printf("wrong result type");

         *va_arg(vl, int *) = (int)lua_tonumber(L, iRetCnt);

         break;

     case 's': /* string result */

         if (!lua_isstring(L, iRetCnt))

         printf("wrong result type");

         *va_arg(vl, const char **) = lua_tostring(L, iRetCnt);

         break;

     default:

         printf("invalid option (%c)", *(sig - 1));

     }  

     iRetCnt++;

     *sig++;

}

va_end(vl);

}

3.LUA可以调用自定义的C函数 (lua调用C函数)

在本节中我会介绍如何提供C函数给LUA在.lua文件中调用

这儿有一个重要的概念:用来交互的栈不是全局变量,每一个函数都有他自己的私有栈

当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置

函数注册到LUA中

任何在Lua中注册的函数必须有同样的原型,这个原型声明定义就是lua.h中的lua_CFunction:

 

typedef int (*lua_CFunction) (lua_State *L);

 

 

static int l_sin (lua_State *L)

{

double d = lua_tonumber(L, 1); /* get argument */

lua_pushnumber(L, sin(d)); /* push result */

return 1; /* number of results */

}

 

 

 

还必须首先注册这个函数。我们使用lua_pushcfunction来完成这个任务

lua_pushcfunction(l, l_sin); //将类型为function的值入栈,

lua_setglobal(l, "mysin"); //将function赋值给全局变量mysin

static int l_sin (lua_State *L)

{

double d = luaL_checknumber(L, 1);

lua_pushnumber(L, sin(d));

return 1; /* number of results */

     }

 

static int l_dir (lua_State *L)

{

DIR *dir;

struct dirent *entry;

int i;

const char *path = luaL_checkstring(L, 1);//函数用来检测参数是否为字符

/* open directory */

dir = opendir(path);

if (dir == NULL)

{ /* error opening the directory? */

     lua_pushnil(L); /* return nil and ... */

     lua_pushstring(L, strerror(errno)); /* error message */

     return 2; /* number of results */

}

/* create result table */

lua_newtable(L);

i = 1;

while ((entry = readdir(dir)) != NULL)

{

     lua_pushnumber(L, i++); /* push key */

     lua_pushstring(L, entry->d_name); /* push value */

     lua_settable(L, -3);

}

closedir(dir);

return 1; /* table is already on top */

}

一个Lua库实际上是一个定义了一系列Lua函数的chunk,并将这些函数保存在适当的地方,通常作为table的域来保存Lua的C库就是这样实现的。除了定义C函数之外,还必须定义一个特殊的用来和Lua库的主chunk通信的特殊函数一旦调用,这个函数就会注册库中所有的C函数,并将他们保存到适当的位置。

 

 

 

 

4.运行LUA文件的函数(调用LUA文件运行函数)

lua_State* m_pLuaState = lua_open();

luaL_openlibs(m_pLuaState);

if(luaL_dofile(m_pLuaState, strLuaFilePath.c_str()))

{//直接运行lua中独立的函数

     return false;

}

return true;

5.运行LUA函数的函数(调用LUA代码片断的函数)

1.     选注册必要的C函数(直接调用非C的lua函数也行: 3.LUA可以调用自定义的C函数)

2.     把注册的函数写入LUA

3.     运行LUA函数的函数

//开放给doluafunc使用的函数,autoluafunc_XXXX

char szTemp[256] = {0};

_snprintf_s(szTemp, sizeof(szTemp), "function autoluafunc_%s()\n %s();\n end\n",函数名, 函数名);

luaL_dostring(m_pLuaState, szTemp);//放入LUA调用表中

 

char szTemp[256] = {0};

_snprintf_s(szTemp, sizeof(szTemp), "autoluafunc_%s",函数名);

lua_getglobal(m_pLuaState, szTemp);

int ret = lua_pcall(m_pLuaState, 0, 0, 0);

由以上代码看来运行LUA函数的函数都是无参数的LUA

看上去没有什么用的代码段却在ini配置等方面有很好的应用,如ini中配置一串lua函数,你可以把这一串lua函数用一个特定的无参lua重新定义一遍,然后使用此lua调用,具体的过程还要通过项目实践才会了解~

 

 

 

以上是我个人总结的一些lua的应用,方便以后开发,如有不明白可以给我留言,没能详尽描述也请原谅.

你可能感兴趣的:(LUA学习笔记)