Lua虚拟栈
Lua和C++交互,必须通过Lua虚拟栈,所以首先要理解Lua虚拟栈。
栈的特点是先进后出,在Lua中,Lua堆栈是一个struct,它的索引可以是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶,lua的栈是在lua_State的时候创建的。
- lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.
- lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收.
Lua栈的操作,Lua与C/C++是通过栈来通信,Lua提供了C API对栈进行操作
操作需要操作Lua栈,需要导入lua.h,lauxlib.h,lualib.h,luaconf.h四个头文件
#include <stdio.h>
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
void main()
{
lua_State *L = luaL_newstate();
//1.基本的库都加载进来
/* 官网文档提到不要直接调用 luaopen函数
//打开基础库
luaopen_base(L);
//打开表
luaopen_table(L);
//打开io
luaopen_io(L);
//打开字符串库
luaopen_string(L);
//打开数学库
luaopen_math(L);
*/
lua_cpcall(L, luaopen_base, NULL);
lua_cpcall(L, luaopen_table, NULL);
lua_cpcall(L, luaopen_io, NULL);
lua_cpcall(L, luaopen_string, NULL);
lua_cpcall(L, luaopen_math, NULL);
//2.入栈操作
lua_pushstring(L, "Hello world");
lua_pushnumber(L,20);
//3.取值操作
if( lua_isstring(L,1)){ //判断是否可以转为string
printf("%s",lua_tostring(L,1));
}
if( lua_isnumber(L,2)){
printf("%s",lua_tonumber(L,1));
}
//关闭State
lua_close(L);
}
C++调用Lua
lua和c通信时有这样的约定: 所有的lua中的值由lua来管理, c++中产生的值lua不知道, 类似表达了这样一种意思: “如果你(c/c++)想要什么, 你告诉我(lua), 我来产生, 然后放到栈上, 你只能通过api来操作这个值, 我只管我的世界”, 这个很重要, 因为:
“如果你想要什么, 你告诉我, 我来产生”就可以保证, 凡是lua中的变量, lua要负责这些变量的生命周期和垃圾回收, 所以, 必须由lua来创建这些值(在创建时就加入了生命周期管理要用到的簿记信息)”然后放到栈上, 你只能通过api来操作这个值”, lua api给c提供了一套完备的操作界面, 这个就相当于约定的通信协议, 如果lua客户使用这个操作界面, 那么lua本身不会出现任何”意料之外”的错误.”我只管我的世界”这句话体现了lua和c/c++作为两个不同系统的分界, c/c++中的值, lua是不知道的, lua只负责它的世界
Lua代码:Test.lua
str = "Hello Lua"
tab = {name = "ZhangSan",age = 22}
function Add(a,b)
print(a + b);
return a + b;
end
现在通过Test.cpp执行它
#include <stdio.h>
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
void main()
{
lua_State *L = luaL_newstate();
{
//直接执行Lua代码
luaL_dostring(L, "print('Hello Lua')");
//执行Lua文件代码
luaL_dofile(L, "Test.lua");
//读取变量
//查找到变量,并压入栈
lua_getglobal(L,"str");
string str = lua_tostring(L,-1);
//读取table
lua_getglobal(L,"tab");
//把 index[k] 的值压栈,从-1的位置获取name的值,并将它压栈
lua_getfield(L,-1,"name");
str = lua_tostring(L,-1);
//修改table
// 将需要设置的值设置到栈中
lua_pushstring(L, "我是一个大帅锅~");
// 将这个值设置到table中(此时tbl在栈的位置为2)
lua_setfield(L, 2, "name");
//创建一个新table
// 创建一个新的table,并压入栈
lua_newtable(L);
// 往table中设置值
lua_pushstring(L, "Give me a girl friend !"); //将值压入栈
//将值设置到table中,并将Give me a girl friend 出栈
lua_setfield(L, -2, "str");
//读取函数
lua_getglobal(L, "Add");
//第一个参数压栈
lua_pushinteger(L, 6);
//第二个参数压栈
lua_pushinteger(L, 5);
//调用Add函数,同时会对Add函数及两个参数进行出栈,并压入返回值
lua_call(L, 2, 1);//2:参数个数,1:返回值个数
int result = lua_tointeger(L, -1);//从栈中取返回值,也就是获取栈顶
lua_pop(L, 4);//清栈
}
//关闭State
lua_close(L);
}
重点:堆栈操作是基于栈顶的,就是说它只会去操作栈顶的值
Lua调用C++
在lua中注册自己的函数,函数必须遵循规范(可在lua.h中查看)如下:
typedef int (*lua_CFunction) (lua_State *L);
换句话说,所有的函数必须接收一个lua_State作为参数,同时返回一个整数值。因为这个函数使用Lua栈作为参数,所以它可以从栈里面读取任意数量和任意类型的参数。而这个函数的返回值则表示函数返回时有多少返回值被压入Lua栈。(因为Lua的函数是可以返回多个值的)
1.直接将模块写入Lua源码中,单个函数注册
我们在Cpp文件加入如下函数:
int GetTwoVar(lua_State * L)
{
// 向函数栈中压入2个值
lua_pushnumber(L, 10);
lua_pushstring(L, "hello world");
int a = lua_tonumber(L, 1);
int b = lua_tointeger(L, 2);
printf("A = %d,B = %d;", a, b);
return 2;
}
在main函数里加入:
lua_register(L, "GetTwoVar", GetTwoVar);
/* lua_register :做了两步,第一步将GetTwoVar压栈,
第二步:将栈元素链接表中元素,做成一对键值表
相等于:
lua_pushcfunction(L, getTwoVar); //将函数放入栈中
lua_setglobal(L, "getTwoVar"); //设置lua全局变量getTwoVar
*/
luaL_dostring(L,"print(GetTwoVar(111,999))");
一般我们不建议去修改别人的代码,更倾向于自己编写独立的C/C++模块,供Lua调用,下面来讲讲如何实现。
2.使用使用静态依赖的方式
- 新建一个空的win32控制台工程,记得在vc++目录中,把lua中的头文件和lib文件的目录包含进来,然后->链接器->附加依赖项->将lua51.lib和lua5.1.lib也包含进来。
- 在目录下新建一个avg.lua如下:
avg, sum = average(10, 20, 30, 40, 50)
print("The average is ", avg)
print("The sum is ", sum)
3.新建test.cpp如下:
#include <stdio.h>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
/* 指向Lua解释器的指针 */
lua_State* L;
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;
}
int main ( int argc, char *argv[] )
{
/* 初始化Lua */
L = lua_open();
/* 载入Lua基本库 */
luaL_openlibs(L);
/* 注册函数 */
lua_register(L, "average", average);
/* 运行脚本 */
luaL_dofile(L, "avg.lua");
/* 清除Lua */
lua_close(L);
/* 暂停 */
printf( "Press enter to exit…" );
getchar();
return 0;
}
3.使用dll动态链接的方式:批量注册,不加载到全局库里,推荐使用
我们先新建一个dll工程,工程名为mLualib。(因此最后导出的dll也为mLualib.dll)
然后编写我们的c++模块,以函数为例,我们先新建一个.h文件和.cpp文件
h文件如下:
#pragma once
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#ifdef LUA_EXPORTS
#define LUA_API __declspec(dllexport)
#else
#define LUA_API __declspec(dllimport)
#endif
extern "C" LUA_API int luaopen_mLualib(lua_State *L);//定义导出函数
.cpp文件如下:
#include <stdio.h>
#include "mLualib.h"
static int averageFunc(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; //返回两个结果
}
static int sayHelloFunc(lua_State* L)
{
printf("hello world!");
return 0;
}
static const struct luaL_Reg myLib[] =
{
{"average", averageFunc},
{"sayHello", sayHelloFunc},
{NULL, NULL} //数组中最后一对必须是{NULL, NULL},用来表示结束
};
int luaopen_mLualib(lua_State *L)
{
luaL_register(L, "ss", myLib);
return 1; // 把myLib表压入了栈中,所以就需要返回1
}
然后编译它,然后新建一个lua文件,在lua中我们这样子来调用:(调用之前记得把dll文件复制到lua文件目录下)
require "mLualib"
local ave,sum = ss.average(1,2,3,4,5)//参数对应堆栈中的数据
print(ave,sum) -- 3 15
ss.sayHello() -- hello world!
至此都发生了什么呢?梳理一下:
1.我们编写了averageFunc求平均值和sayHelloFunc函数,
2.然后把函数封装myLib数组里面,类型必须是luaL_Reg
3.由luaopen_mLualib函数导出并在lua中注册这两个函数。
4.加载到全局库,这样会使全局库变乱,不推荐使用
#include <stdio.h>
#include<string.h>
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
#pragma comnent(lib,"lua5.1.lib");
int GetTwoVar(lua_State * L)
{
// 向函数栈中压入2个值
lua_pushnumber(L, 10);
lua_pushstring(L, "hello world");
int a = lua_tonumber(L, 1);
int b = lua_tointeger(L, 2);
printf("A = %d,B = %d;", a, b);
return 2;
}
luaL_Reg funcs[] =
{
{"GetTwoVar1",GetTwoVar},
{ "GetTwoVar2",GetTwoVar },
{ "GetTwoVar3",GetTwoVar },
{ "GetTwoVar4",GetTwoVar },
{ "GetTwoVar5",GetTwoVar },
{0,0},
};
void main()
{
lua_State* L = luaL_newstate();
//加载基础库
lua_openlib(L);
//Lua调用C++函数
//函数要遵循规范(可在lua.h中查看)如下:
//typedef int (*lua_CFunction) (lua_State *L);
//所有的函数必须接收一个lua_State作为参数,
// 同时返回一个整数值。因为这个函数使用Lua栈作为参数,
// 所以它可以从栈里面读取任意数量和任意类型的参数。
// 而这个函数的返回值则表示函数返回时有多少返回值被压入Lua栈。
// (因为Lua的函数是可以返回多个值的)
//Lua调用C++函数,批量注册到Lua
//:加载到全集库
//1:状态机,2:库名称,3:函数的地址数组,4:是否加到系统项上
luaL_openlib(L, "myLid", funcs, 0);
luaL_dostring(L, "myLid.GetTwoVar1(999,0)");
luaL_dostring(L, "myLid.GetTwoVar2(888,0)");
luaL_dostring(L, "myLid.GetTwoVar3(777,0)");
lua_close(L);
}
需要注意的是:函数参数里的lua_State是私有的,每一个函数都有自己的栈。当一个C/C++函数把返回值压入Lua栈以后,该栈会自动被清空。