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 传递Table给Lua
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
(2)Lua调用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并压入栈中,并预分配narr个array元素的空间和预分配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_settable,index取你要设置的table在栈中的索引,调用完该方法后,压入的key和value会自动被弹出栈。
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); //获取索引为index的table中指定key的value,key要预先压入栈,函数调用结束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/_ENV、upvalue三种方式存取全局数据。
l registy方式
registry是一个Lua的全局Table,且只有在Lua的C API中才可以访问它。存取时要使用LUA_REGISTRYINDEX的“伪索引”来标识它在lua栈的位置。
例如,模块A向registry写入key-value,模块B从registy读取。
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中的内嵌函数f2,f2引用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
模块加载机制
Lua用require来加载模块。例如:
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))
以上代码 test1和test2打印的指针相同。
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() -- 真正打开库