答小白

Good Luck!

Lua基本用法

Lua基本用法

一、     C++和Lua相互传递数据

二者数据的传递依靠Lua虚拟的堆栈。

(1)C++访问/修改Lua

在C/C++中可以创建Lua虚拟机,运行lua脚本,调用Lua函数,获取Lua全局变量值,修改Lua全局变量值,传递table给Lua。

l  运行Lua脚本

std::string scriptPath = FileUtils::getInstance()->fullPathForFilename("hello.lua");
int status = luaL_loadfile(lua_state, scriptPath.c_str());
std::cout << " return: " << status << std::endl;
int result = 0;
if(status == LUA_OK)
{
    result = lua_pcall(lua_state, 0, LUA_MULTRET, 0);
}
else
{
    std::cout << " Could not load the script." << std::endl;
}

 

l  调用Lua函数

Lua文件

-- add two numbers
function add ( x, y )
    return x + y
end

在C++程序中调用该lua函数

int luaAdd(lua_State *lua_state , int x, int y)
{
    int sum;
    //获取lua里面的add函数并把它放到lua的栈顶
    lua_getglobal(lua_state, "add");
    //lua栈里面压入两个参数

    lua_pushnumber(lua_state, x);
    lua_pushnumber(lua_state, y);
    //调用lua函数,这里的2是参数的个数,1是返回值的个数

    lua_call(lua_state, 2, 1);
    //从栈顶读取返回值,注意这里的参数是-1
    sum = lua_tointeger(lua_state, -1);
    //最后我们把返回值从栈顶拿掉

    lua_pop(lua_state, 1);
    return sum;
}

 

l  获取Lua全局变量的值

Lua文件

myname = "子龙山人"

C++中访问该全局变量

lua_getglobal(lua_state, "myname");
std::string myname = lua_tostring(lua_state, -1);
lua_pop(lua_state, 1);
std::cout<<"Hello: "<<myname<<std::endl;

 

l  修改Lua全局变量的值

Lua文件

myname = "子龙山人"

C++中修改该全局变量

lua_pushstring(lua_state, "World");
lua_setglobal(lua_state, "myname");

 

l  传递TableLua

C++中创建table,并设置为lua全局变量

lua_createtable(lua_state, 2, 0);
lua_pushnumber(lua_state, 1);
lua_pushnumber(lua_state, 49);
// lua_settable(lua_state, -3);
lua_rawset(lua_state, -3);
lua_pushnumber(lua_state, 2);
lua_pushstring(lua_state, "Life is a beach");
// lua_settable(lua_state, -3);
lua_rawset(lua_state, -3);
lua_setglobal(lua_state, "arg");

这个table{49,”Life is a beach”}

lua脚本里面可以这样子来访问这个table:

for i=1,#arg do
    print("      ", i, arg[i])
end

 

2Lua调用C/C++函数

每当Lua调用C/C++函数,都会获得一个新的堆栈,该堆栈初始包含所有的调用C/C++函数所需要的参数值(Lua传给C/C++函数的实参),并且函数执行完毕后,会把返回值压入这个栈(Lua从中拿到C函数调用结果)。当一个c/c++函数把返回值压入Lua栈以后,该栈会自动被清空。

可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即typedef int (*lua_CFunction)(lua_State* L)实现者可以通过该指针进一步获取Lua代码中实际传入的参数。返回值是整型,表示该C函数将返回给Lua代码的返回值数量,如果没有返回值,则return 0即可。C函数无法直接将真正的返回值返回给Lua代码,而是通过虚拟栈来传递Lua代码和C函数之间的调用参数和返回值的。

例如:

static int average(lua_State *L)

{

    /* 得到参数个数 */

    int n = lua_gettop(L);

    double sum = 0;

    int i;

 

    /* 循环求参数之和 */

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

    {

        /* 求和 */

        sum += lua_tonumber(L, i);

    }

    /* 压入平均值 */

    lua_pushnumber(L, sum / n);

    /* 压入和 */

    lua_pushnumber(L, sum);

    /* 返回返回值的个数 */

    return 2;

}

 

Lua调用C/C++函数,有两种规则:

l  C/C++函数注册为Lua全局变量

void ALuaTestActor::TestLua2Cpp()

{

    /* 初始化Lua */

    lua_State* L = luaL_newstate();

 

    /* 载入Lua基本库 */

    luaL_openlibs(L);

    /* 注册函数 */

    lua_register(L, "average", average);

    /* 运行脚本 */

    int bRet = luaL_dofile(L, LuaTest.lua");

    if (bRet)

    {

        UE_LOG(LogTemp, Log, TEXT("luaL_loadfile error, ret=%d"), bRet);

        return;

    }

 

    /* 清除Lua */

    lua_close(L);

}

Lua文件:

avg, sum = average(10, 20, 30, 40, 50)

print("The average is ", avg)

print("The sum is ", sum)

 

l  C/C++函数库成为Lua的模块

/*

void lua_createtable (lua_State *L, int narr, int nrec);  //创建一个空的table并压入栈中,并预分配narrarray元素的空间和预分配nrec个非array元素的空间

void lua_newtable (lua_State *L); // lua_createtable的特例版,相当于调用 lua_createtable(L, 0, 0)

 

void lua_settable (lua_State *L, int index);//在调用之前需要先压入你要设置的 key value,例如你要设置table["foo"] = "bar",先把 "foo" 压入栈中,再把 "bar" 压入栈中,然后再调用lua_settableindex取你要设置的table在栈中的索引,调用完该方法后,压入的keyvalue会自动被弹出栈。

void lua_rawset (lua_State *L, int index);//lua_rawset除了设置时不会触发元表操作外和lua_settable基本相同

void lua_setfield (lua_State *L, int index, const char *k);//将当前栈顶元素值赋值给index_tbl[k],并将栈顶元素出栈

void lua_rawseti (lua_State *L, int index, int n);//lua_rawseti lua_setfield类似,但是只能设置整数类型的key,可以对于纯数字索引的数组使用。

 

 

void lua_gettable (lua_State *L, int index); //获取索引为indextable中指定keyvaluekey要预先压入栈,函数调用结束key会被弹出栈,并把value压入栈中

void lua_rawget (lua_State *L, int index);  // lua_gettable类似,但不涉及元表

void lua_getfield (lua_State *L, int index, const char *k); //key不需要压入栈,直接当作第三个参数传入,得到的value同样会被压入栈中

void lua_rawgeti (lua_State *L, int index, int n); // 除了不涉及元表,并且key只能为整数

 

void lua_call (lua_State *L, int nargs, int nresults);//lua_call要求首先压入要调用的lua函数,然后依次按顺序压入nargs个参数, 这时调用lua_call,之后这个函数和所有的参数都会从栈中弹出,最后把函数返回的nresults个结果依次压入栈中。

int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc);//lua_pcall在保护模式下执行lua函数,当执行出错时这个错误会被捕获,而不会直接终止程序运行,lua_pcall的返回值是一个错误码,当返回0时表示没有错误,此时效果和lua_call一样,当返回不为0时表示有错误,并将错误消息压入栈中,第四个参数还可以指定一个错误处理函数的索引,如果不指定可以传入0值。

*/

 

static int sum(lua_State *L)

{

    int a = lua_tointeger(L, 1); //第一个加数,函数的第一个参数总是索引1

    int b = lua_tointeger(L, 2);  //第二个加数

    lua_pushinteger(L, a + b);  //压入结果         

    return 1;                                       //返回1表示该方法只有一个返回值

}

 

static int split(lua_State *L)

{

    size_t len;

    const char *str = lua_tolstring(L, 1, &len);

    const char *sep = lua_tostring(L, 2);

    int lastpos = -1, i = 0, key = 1;

 

    lua_newtable(L); //创建一个table作为返回值

    for (; i < len; i++)

    {

        if (str[i] == *sep)

        {

            lua_pushlstring(L, str + lastpos + 1, i - lastpos - 1);  //压入子串

            lua_rawseti(L, -2, key);  //把子串添加进table

            lastpos = i;

            key++;

        }

    }

    //处理最后一个子串

    lua_pushlstring(L, str + lastpos + 1, len - lastpos - 1);

    lua_rawseti(L, -2, key);

    return 1; //返回table

}

 

static int array_sum(lua_State *L)

{

    //检查参数是否为一个table, 如果不是返回nil

    if (!lua_istable(L, 1))

    {

        lua_pushnil(L);

        return 1;

    }

    int sum = 0;

    int len = lua_objlen(L, 1);  //返回数组的长度

 

    int i = 1;  //lua table 索引从1开始

    for (; i <= len; i++)

    {

        lua_rawgeti(L, 1, i);

        sum += lua_tointeger(L, -1);

    }

 

    lua_pushinteger(L, sum);

    float aver = (float)sum / len; //平均值

    lua_pushnumber(L, aver);

 

    return 2;

}

 

static int array_map(lua_State *L)

{

    //校验参数类型

    if (!lua_istable(L, 1) || !lua_isfunction(L, 2))

    {

        lua_pushnil(L);

        return 1;

    }

    int len = lua_objlen(L, 1); //数组的大小

    int i = 1;

 

    for (; i <= len; i++)

    {

        //由于方法调用之后会被弹出,所以调用之前先复制一份,pushvalue会将指定索引的值复制一份到栈顶

        lua_pushvalue(L, 2);

        lua_rawgeti(L, 1, i); //获取数组元素值并会压入栈中

        lua_call(L, 1, 1);    //调用函数,一个参数 一个返回值,此时复制的函数和压入的参数都会弹出,然后压入结果

        lua_rawseti(L, 1, i);  //再把结果替换掉数组中的原值。

    }

    lua_pushvalue(L, 1);  //把数组复制一份放到栈顶 当作返回值

    return 1;

}

 

//声明一个luaL_Reg结构体数组

static const struct luaL_Reg funcs[] = {

        {"sum", sum},

        {"split", split},

        {"array_sum", array_sum},

        {"array_map", array_map},

        {NULL, NULL}    // 该数组最后一个元素始终是 {NULL, NULL}

};

 

int luaopen_clib(lua_State *L)

{

    luaL_register(L, "clib", funcs);        //第二个参数要和你的模块名一致

    return 1;

}

可以将以上代码带包成一个so/dll模块,然后主程序中注册该模块:

void TestLua2CppWithModule()

{

    /* 初始化Lua */

    lua_State* L = luaL_newstate();

 

    /* 载入Lua基本库 */

    luaL_openlibs(L);

    /* 注册C函数模块 */

    luaopen_clib(L);

    /* 运行脚本 */

    int bRet = luaL_dofile(L, "LuaTest2.lua");

    if (bRet)

    {

        UE_LOG(LogTemp, Log, TEXT("luaL_loadfile error, ret=%d"), bRet);

        return;

    }

 

    /* 清除Lua */

    lua_close(L);

}

 

LuaTest2.lua文件:

local clib = require "clib"

 

local s = clib.sum(3, 99)

print(s)

print("---------------------")

 

local str = "aaa,bbbb,cccc,dddd,eeee"

local arr = clib.split(str, ",")

for i = 1, #arr do

        print(arr[i])

end

print("---------------------")

 

local arr1 = {1, 2, 3, 10}

local sum = clib.array_sum(arr1)

print("sum=", sum)

print("---------------------")

 

local function double(num)

        return num * 2

end

local arr2 = {1, 2, 3, 4, 5}

local res = clib.array_map(arr2, double)

 

for i = 1, #res do

        print(res[i])

end

 

 

二、     Lua中的全局环境变量

C函数里面,当我们需要保存函数里面的一些状态的时候,我们一般采用全局变量或者静态变量的方式。Lua中,可以用registry_G/_ENVupvalue三种方式存取全局数据。

l  registy方式

registry是一个Lua的全局Table,且只有在LuaC API中才可以访问它。存取时要使用LUA_REGISTRYINDEX的“伪索引”来标识它在lua栈的位置。

例如,模块Aregistry写入key-value,模块Bregisty读取。

void lua_registry_set(lua_State* L)

{

    lua_pushstring(L, "key");

    lua_pushstring(L, "value");

    lua_settable(L, LUA_REGISTRYINDEX);

}

 

void lua_registry_get(lua_State* L)

{

    lua_getfield(L, LUA_REGISTRYINDEX, "key");

    const char *str = lua_tostring(L, -1);

 

    FString fstr(str);

    UE_LOG(LogTemp, Log, TEXT("lua_registry_get=%s"), *fstr);

 

    lua_pop(L, 1);

}

 

l  _G/_ENV

lua5.1中,全局变量都保存在_G表中,且可以通过setfenv(n,table)来改变一个函数的环境。

function foo()

local newenv = {a=1}

setmetatable(newenv, {__index=_G})

setfenv(1, newenv)

 

print(a) –输出:1

end

 

lua5.1中,下面的全局变量会写入到_G表中:

a = 1

print(_G[“a”]) – 输出:1

 

lua5.2以后,在保留全局变量_G的同时,引入_ENV作为chunk环境变量。_ENV并不是全局变量,而是一个upvalue,它作用于用load或者loadfile加载的chunk代码,_ENV作为一个upvalue值,它被chunk代码中所有函数共享。

lua加载一段chunk后,_ENV默认值和_G相同,但可以在chunk中修改自己的_ENV

local ORIGIN_ENV=_ENV

 

function chunk_1()

    io.write("a1="..tostring(a) .. "\r\n")

    io.write("b1="..tostring(b) .. "\r\n")

end

 

function chunk_2()

    io.write("a2="..tostring(a) .. "\r\n")

    io.write("b2="..tostring(b) .. "\r\n")

end

 

function main()

    -- 设置ENV

    local newev = {a=1}

    setmetatable(newev, {__index = ORIGIN_ENV})

    _ENV = newev

   

    chunk_1() 

 

    -- 设置ENV

    local newev2 = {b=2}

    setmetatable(newev2, {__index = ORIGIN_ENV})

    _ENV = newev2

   

    chunk_2()

end

 

main()

以上代码载入并执行后,输出:

a1=1

b1=nil

a2=nil

b2=2

 

chunk代码中用lua写入的全局变量,是被写入到_ENV中,而不是_G中。

local newenv={}

setmetatable(newenv, {__index=_ENV})

_ENV=newenv

 

function gwrite()

x=10  -- 写入到 _ENV.x_ENV被本chunk代码所共享

end


gwrite()

 

io.write("inner ENV.x="..tostring(_ENV.x) .. "\r\n")  --输出:10

io.write("inner G.x="..tostring(_G.x) .. "\r\n") –输出:nil

 

_ENV中保存了_G的索引:_ENV._G=_G

 

l  upvalue方式

内嵌函数可以访问外包函数已经创建的局部变量,而这些局部变量则称为该内嵌函数的外部局部变量(或者upvalue,相当于C中的局部静态变量。

function f1()

    local x=1

 

    local function f2()

        print(x)

        x=x+1

    end

 

    return f2

 

end

 

f = f1()

f()  --输出:1

f()  --输出:2

 

以上代码装载并执行后,f是生成的闭包,其原型是f1中的内嵌函数f2f2引用x作为upvalue值。执行完f1()后,闭包会把堆栈上的x复制出来,共享给生成的闭包f,这样每次闭包f的执行,都会对x这个upvalue做修改。


 

三、     Lua模块与包

Lua模块

Lua的模块是由变量、函数等已知元素组成的table,最后返回这个table。例如:

-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = 
{val=100}
 
-- 定义一个常量
module.constant = 
"这是一个常量"
 
-- 定义一个函数
function module.func1()
    
io.write("这是一个公有函数!\n")
end
 
local function func2()
    
print("这是一个私有函数!")
end
 
function module.func3()
    func2
()
end
 
return module

 

模块加载机制

Luarequire来加载模块。例如:
require("module")
print(module.constant)
module.func3
()

 

通常会给加载的模块定义一个别名变量,方便调用

local m = require("module")
print(m.constant)
m.func3
()

 

require函数加载模块有自己的文件路径加载策略,它会尝试从Lua文件或C程序库中加载模块。require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果找到目标文件,则会调用 package.loadfile 来加载模块。否则,就会去找 C 程序库。搜索C程序库的文件路径是从全局变量 package.cpath 获取,而这个变量则是通过环境变量 LUA_CPATH 来初始。搜索的策略跟上面的一样,只不过现在换成搜索的是 so dll 类型的文件。如果找得到,那么 require 就会通过 package.loadlib 来加载它。

 

require会判断是否文件已经加载避免重复加载同一文件。如果需要反复加载,那么第二次加载前需要加一句:package.loaded[luafile] = nil

local test1 = require('module')

local test2 = require(' module ')

print("test1="..tostring(test1))

print("test2="..tostring(test2))

以上代码 test1test2打印的指针相同。

 

C

Lua提供loadlib函数加载指定的库并且链接到lua,返回指定的lua函数,这样就可以直接在lua中调用它。例如:

local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f
()  -- 真正打开库


发表评论: