Lua 和 C++混合编程

一、为什么需要Lua 和 C++

是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。

  • lua是西班牙语的月亮

Lua优点

  • 最快的脚本语言

  • 可以编译调试

  • 与C/C++结合容易

  • Lua 是对性能有要求的必备脚本

Lua应用

  • 魔兽世界、博德之门、仙剑奇侠传五、愤怒的小鸟等游戏的任务脚本和后端服务器。

  • Google的大量应用,和战胜李世石的AlphaGo

  • 视频服务器、入侵检测系统脚本、防火墙脚本

二、开发测试环境准备(Lua源码编译)

开发环境准备

  • 下载 Lua 5.4.4 http://www.lua.org/versions.html

  • 安装 vs2019

Lua编译

  • 编译Lua为动态库 (不用静态库,影响c++编译速度)

  • 建立Lua编译器项目

  • 编译Lua库为了以后对其拓展(也可以直接下载库)

启动VS2019,创建新项目,选择“Windows桌面向导”,点击“下一步”。

Lua 和 C++混合编程_第1张图片

设置项目名称,这里我设置的名称是“lua-5.4.1”,点击“创建”。

Lua 和 C++混合编程_第2张图片

此时会弹出一个对话框,下拉应用程序类型选择“静态库”或者动态库,选择“空项目”,点击“确认”,等待项目创建完毕。

Lua 和 C++混合编程_第3张图片

创建好项目后,在【头文件】选项上点击鼠标右键,选择【添加】—>【现有项】,然后找到刚刚解压Lua压缩包的目录,选择Lua目录下的src文件夹,这里我的是D:\lua-5.4.1\src目录。然后把src下的所有.h文件添加到头文件。

Lua 和 C++混合编程_第4张图片

同上操作,在【源文件】选项上点击鼠标右键,以此选择【添加】—>【现有项】,找到Lua目录。把src下除去“lua.c”和“luac.c”后的所有.c文件都添加到源文件。

注意: 需要注意的是 lua.c 和luac.c 不能拷贝进去,这两个文件不能编译。

Lua 和 C++混合编程_第5张图片

导入头文件和源文件之后,右键【项目】—>【属性】,在界面中操作,【C/C++】—> 【高级】—> 【编译为】选择【编译为C代码(/TC)】,最后确定。

Lua 和 C++混合编程_第6张图片

添加预处理器 LUA_BUILD_AS_DLL

Lua 和 C++混合编程_第7张图片

把Debug替换成Release。

Lua 和 C++混合编程_第8张图片

生成静态库或者动态库。

Lua 和 C++混合编程_第9张图片

显示生成成功。

Lua 和 C++混合编程_第10张图片

打开项目目录–找到release文件夹–找到lua5.3.lib(你自定义项目名称)。

Lua 和 C++混合编程_第11张图片

使用Lua库

上面我们已经编译好了Lua5.4.1的库文件,下面进行测试lua库是否可以使用。

创建一个控制台应用

Lua 和 C++混合编程_第12张图片

右键单击项目–选择属性。

选择C/C+±-常规–附加包含目录–选择lua源码目录的src文件夹。

Lua 和 C++混合编程_第13张图片

选择链接器–常规–附加库目录–选择Lua库文件目录

Lua 和 C++混合编程_第14张图片

选择链接器–输入–附加依赖项–输入我们所编译的lua5.4.1.lib库文件名称(刚才生成后拷贝到Lua源码目录下的静态链接库)。然后点击应用,确定就好了。

Lua 和 C++混合编程_第15张图片

到此Lua开发环境就配置好了,下面测试搭建的Lua环境。

在源代码里创建一个main.lua的文件,如图所示。

Lua 和 C++混合编程_第16张图片

创建好后,在里面输入打印代码:

print("Hello Lua")

如图所示。

Lua 和 C++混合编程_第17张图片

然后在主函数里面,编写测试代码并运行:


extern "C"
{
#include 
#include 
#include 
}

int main()
{
    //lua_State* lua = lua_open();
    lua_State* lua = luaL_newstate();
    //打开基本库
    luaopen_base(lua);
    //打开string库(使用string时需要打开)
    luaopen_string(lua);
    //打开table库
    luaopen_table(lua);
    //打开IO库
    luaL_openlibs(lua);
    //打开math库
    luaopen_math(lua);

    if (luaL_loadfile(lua, "main.lua"))
    {
        const char* error = lua_tostring(lua, -1);

        printf("%s\n 执行脚本文件失败", error);

        return -1;
    }

    if (lua_pcall(lua, 0, 0, 0))
    {
        const char* error = lua_tostring(lua, -1);
        printf("%s\n 执行脚本文件失败", error);

        return -1;
    }

    lua_close(lua); //释放lua内存
    return 0;
}

三、Lua基本语法 (核心语法)

Lua 基础数据类型和变量

  • 全局变量

b = 2 

本地变量 (尽量用本地,保证及时的垃圾回收)

local  a = 1

Lua 基础数据类型

nil

  • 用于区分具有一些数据或没有(nil)数据的值

  • 全局变量设为nil会交给垃圾回收

local a = nil
print(type(a))   -- > nil

Booleans

注意Lua中所有的值都可以作为条件

除了false和nil为假,其他值都为真,0为真

local a = true

number

Lua中没有整数,都是用浮点数运算

对应c的double类型

新版Lua中有基于64位的整形

tonumber 转换格式

local a = "123"         --定义一个string类型数据
local b= tonumber(a)    --转换成number
print(type(b))          --输出:number
print(b)                --输出:123

string

tostring转换格式

local a = 123           -- 定义一个number类型数据
local b= tostring(a)    -- 转换成string

[[ ]] 多行字符串赋值

”与C一样的转义\””

local html = [[


]]
print(html)

字符串拼接 ..

local str = "test1" .. "test2"
print(str)        -- 输出:test1test2

字符串长度 string.len

字符串子串 string.sub(str,3,5)

local str = "12345678"                --定义一个string类型数据
print(string.len(str))                --输出str的长度:8

print("sub string = "..string.sub(str,3,5))  --输出:sub string = 345

字符串查找 local b,e = string.find(str,"HEAD") 支持正则

local str = "12345678"
local b,e = string.find(str,"345"); --查询str中是否有345子串,成功返回子串开始和结束位置,失败返回nil
print("b = "..b .."  e="..e);        --输出:b = 3  e=5

字符串替换 string.gsub(str,"HEAD","XCJ") 支持正则

local str = "12341234"
local str2 = string.gsub(str,"12","ab") --把str中"12"替换成"ab"
print(str2)                --输出:ab34ab34

Lua 控制结构语句

if 条件语句

  • and or not

  • if(a==1 or a==3)

  • if(a==1 and b == 1)

  • if(not (a==1)) 注意加括号,解决逻辑次序问题

  • < > <= >= ~= ==

if((1==1 and 1==3) or 3==3) then
    print("1==1")
elseif (2==2) then    
    print("2==2")    
else
    print("in else")
end

while 循环语句

local i = 100
while(not(i <0)) do
    print("i="..i)
    if(i == 90) then
        print("break while")    --打印100到90
        break
    end
    i = i-1
end

repeat 循环语句(类似c的do while)

local i =100
repeat
    i = i + 1
    print("i = " .. i)    --打印101到111
until i>110

for 循环语句

数值循环

for var = 1,10 do
    print("var = " .. var)    --从1循环到10退出,一次默认加1
end

for var = 1,10,2 do
    print("var = " .. var)    --从1循环到10退出,一次增加2  var值变化为:1,3,5...
end

范型循环

local days={"Sun","Mon","Tue"}
for i,v in pairs(days) do
    print(i.."=="..v)    --输出:1==Sum 2==Mon 3==Tue
end

local tab = { [1] = "A",[2] = "B",[4] = "D"}
for i,v in ipairs(tab) do
    print(i.."--"..v)    //用ipairs时只输出1,2 用pairs输出1,2,4
end

Lua表

  • 表的大小 table.getn(t1)

  • 插入 table.insert(a, pos,line)

  • 不传pos相当于 push_back

  • 删除 table.remove(a,pos) 返回这次删除的值

  • 不传pos相当于 pop_back

-- 数组(保存相同类型)
local tab1 = {"001","002","003"}
table.insert(tab1,2,"002-2")        --在第2个元素后面插入一个元素
table.insert(tab1,"004")            --没有指定插入位置,默认在最后面插入一个元素
table.remove(tab1,1)                --删除指定的第一个元素
table.remove(tab1)                  --删除最后一个元素
for i,v in ipairs(tab1) do
    print("v = ".. v)
end

--哈希表(同时存在不同类型)
local tab2 = {id=123,age=20}
tab2["name"] = "xiaoming"    --插入一个元素
tab2["id"] = nil            --删除一个元素
for i,v in pairs(tab2) do    --注意这里不能用ipairs,ipairs只能遍历数组,在这里遍历不出来
    print("v -- ".. v)
end

--多维数组
local tab3 = {}
tab3[1] = {"name1","value1"}
tab3[2] = {"name2","value2"}
for k,v in pairs(tab3) do
    for k2,v2 in pairs(v) do
        print("k2="..k2,"v2="..v2)
    end
end

Lua函数

函数参数

  • 普通参数

--调用者传参少于定义的参数个数时,没有传的参数默认为nil
--调用者传参多于定义的参数个数时,多余的参数舍弃
function test(p1,p2)
    if(p1 == nil) then
        p1 = "000"
    end
    if(p2 == nil) then
        p2 = "000"
    end

    print("p1="..p1,"p2="..p2)
end

--调用上面定义的函数
test(123)
  • 可变参数

function test(...)
    local arg={...}                --获取参数 {...} 表示一个由所有变长参数构成的数组
    print("arg len is "..#arg)     --打印参数长度
    
       for i,v in ipairs(arg) do
          print(v)                 --打印传进来的每个参数
       end
end

函数返回值

多返回值 (不建议使用,太特殊的用法,可通过直接返回表实现)

function test(p1,p2)
    --可以选择返回任意数量返回值
    return 11,"22"
end

--可以选择接收几个参数,多接收的值为nil,少接收的参数丢弃
local ret1,ret2 = test()
print(ret1..ret2)        --打印:1122

四、Lua调用C++ (函数和部分类调用)

Lua与C++交互次序

Lua 和 C++混合编程_第18张图片

Lua栈的访问下标

Lua 和 C++混合编程_第19张图片

Lua调用C++ 传递普通参数

编写c代码

int CTest(lua_State* L)
{
    size_t len;
    //获取lua传入的第一个参数(string类型),len为参数长度
    const char* name = lua_tolstring(L, 1, &len);
    int age = lua_tonumber(L, 2);//获取lua传入的第二个参数(int类型接收)
    bool is = lua_toboolean(L, 3);//获取lua传入的第三个参数(bool类型接收)
    //打印内容:name = 123abc len = 6 , age = 456 , is = 1
    printf("lua name = %s len = %d , age = %d , is = %d\n", name, len, age, is);
    return 0;
}    

//将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
//在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
lua_register(lua, "CTest", CTest);

编写lua代码

--调用c++定义的函数
CTest("123abc", 456, true)

Lua调用C++ 传递数组

编写c代码

int CTestArr(lua_State* L)
{
    printf("int CTestArr ");
    int len = luaL_len(L, 1);//获取table长度
    printf("len = %d\n", len);
    for (int i = 1; i <= len; i++)
    {
        lua_pushnumber(L, i);//插入数字索引
        lua_gettable(L, 1);//将刚刚插入的数字索引出栈(table[i]),然后压入栈顶
        size_t size;
        const char* str = lua_tolstring(L, -1, &size);//获取压入栈顶的值    获取每个table值
        printf("str = %s\n", str);
        lua_pop(L, 1);    //将刚刚改变的栈恢复
    }

    return 0;
}

//将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
//在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
lua_register(lua, "CTestArr", CTestArr);

编写lua代码

local arr = {"A001","A002","A003","A004"}
--调用c++定义的函数
CTestArr(arr)

Lua调用C++ 传递表和参数类型检查

编写c代码

int CTestTable(lua_State* L)
{
    lua_pushnil(L);//插入一个空的table
    while (lua_next(L, 1) != 0)
    {
        //先push key(先插入栈顶 -2),在push value(后插入栈顶-1)
        printf("key = %s", lua_tostring(L, -2));
        printf("  value = %s\n", lua_tostring(L, -1));

        lua_pop(L, 1);
    }

    //获取栈底中ket为name的值压入栈顶
    lua_getfield(L, 1, "name");
    //获取刚刚压入栈顶的值
    printf("name = %s\n", lua_tostring(L, -1));

    //获取判断第二个参数类型
    //luaL_checktype(L, 1, LUA_TTABLE);
    if (lua_type(L, 2) != LUA_TNUMBER)
    {
        printf("para 2 is not number\n");
    }

    return 0;
}

//将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
//在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
lua_register(lua, "CTestTable", CTestTable);

编写lua代码

local tab = {name="xiaoming",age="22",id="003"}
CTestTable(tab,12)

Lua获取C++返回参数

编写c代码

  • 返回普通参数

int CTestReAdd(lua_State* L)
{
    //检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。
    //如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。
    double op1 = luaL_checknumber(L, 1);
    double op2 = luaL_checknumber(L, 2);
    //将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。
    lua_pushnumber(L, op1 + op2);
    //返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
    return 1;
}
  • 返回表参数

int CTestReAdd(lua_State* L)
{
    //创建一个空表
    lua_newtable(L);
    //插入key value
    lua_pushstring(L, "name");
    lua_pushstring(L, "xiaoming");
    //把值写入刚刚创建的表中
    lua_settable(L, -3);//因为刚刚插入了两个值,所以表的位置在-3

    //在插入一个key value
    lua_pushstring(L, "age");
    lua_pushnumber(L, 33);
    lua_settable(L, -3);//因为上面使用lua_settable把插入的值出栈了,所以刚刚入栈了两个,现在还是-3

    //返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
    return 1;
}

编写lua代码

local re = CTestRe()
--打印接收的普通参数
print(re)

--打印接收的表参数
print("name="..re["name"])
print("age="..re["age"])

五、C++调用Lua (函数调用,获取表)

C++给Lua传递一个全局变量

编写c代码

//c++给lua传递一个全局变量    需要写在调用脚本前面
lua_pushstring(lua, "hello");//把string数据压入栈顶
lua_setglobal(lua, "ctest");//设置lua全局变量ctest,数据为刚刚压入的栈顶的值

//c++给lua传递一个全局表
lua_newtable(lua);//创建一个表放入栈顶
lua_pushstring(lua, "name");//把key压入栈中
lua_pushstring(lua, "xiaoming");//把value压入栈中(string类型)
lua_settable(lua,-3);//把压入的key和value出栈到刚刚创建的表中,因为刚刚新压入了两个数据,所以表的位置在-3
lua_pushstring(lua, "age");//在压入一个新的key到栈中
lua_pushnumber(lua, 22);//在压入一个新的value到栈中(number类型)
lua_settable(lua, -3);//把压入的key和value出栈到刚刚创建的表中
lua_setglobal(lua, "person");//设置lua全局变量person,数据为刚刚压入的栈顶的值

编写lua代码

//获取打印普通变量
print("c++ test = "..ctest)    --打印内容为:c++ test = hello

//获取打印表    下面两种方式都可以获取表中数据
print(person.name)
print(person["age"])

C++获取Lua中定义的全局变量

编写c代码

//获取lua中普通变量        写在调用脚本后面
lua_getglobal(lua, "width");//获取lua中定义的width全局变量,压入栈顶
int width = lua_tonumber(lua, -1);//从栈里面取值
lua_pop(lua, 1);//从栈顶弹出,让栈恢复原状
printf("width = %d\n", width);    //打印内容为:width = 1920

//c++获取lua表数据
lua_getglobal(lua, "conf");//获取lua中定义的conf全局变量,压入栈顶
lua_getfield(lua, -1, "titlename");//获取conf表中key为titlename的数据压入栈顶
printf("title = %s\n", lua_tostring(lua, -1));    //打印内容为:title = first lua
lua_pop(lua, 1);
lua_getfield(lua, -1, "height");
printf("height = %d\n", (int)lua_tonumber(lua, -1));    //打印内容为:height = 1080
lua_pop(lua, 2);//开头lua_getglobal压入的表和上面lua_getfield压入的值一起出栈清理

编写lua代码

//定义普通全局变量
width = 1920

//定义全局table
conf = 
{
    titlename = "first lua",
    height = 1080
}

C++调用Lua函数

编写c代码

//调用lua函数
printf("top is %d\n", lua_gettop(lua));
lua_getglobal(lua, "ferror");//压入一个错误处理函数
int errfun = lua_gettop(lua);//获取此时栈的数量,也是栈底距离刚刚压入的错误处理函数的位置
lua_getglobal(lua, "event");//把要调用的lua函数event压入栈顶

lua_pushstring(lua, "key");//压入一个string类型数据到栈顶,这里压入给函数做参数(做为函数第一个参数)

lua_newtable(lua);//创建一个空表压入栈顶(做为函数第二个参数)
lua_pushstring(lua, "name");//压入一个key值到栈顶
lua_pushstring(lua, "zhangshan");//压入一个value值到栈顶
lua_settable(lua, -3);//将key值和value值给刚刚创建的表并出栈

//上下文 传参的个数(上面压入了几个参数就传几个) 接收返回值的个数 函数出错返回信息
//if (lua_pcall(lua, 1, 1, -3) != 0)
if (lua_pcall(lua, 1, 1, errfun) != 0)//两种方式都可以,区别是错误函数一个从栈顶开始计算,一个从栈底
{
    printf("call event failed %s\n", lua_tostring(lua, -1));//没有接收错误信息时,默认把错误信息压入栈顶
    lua_pop(lua, 1);//把栈顶错误信息出栈,清理堆栈
}
else
{
    //函数执行成功返回值压入栈顶,此时读取
    
    //读取返回的表的返回值
    lua_getfield(lua, -1, "id");//把表key为id的值压入栈顶
    printf("event success %d\n", (int)lua_tonumber(lua, -1));
    lua_pop(lua, 2);

    //读取普通返回值
    //printf("event success %s\n", lua_tostring(lua, -1));
    //lua_pop(lua, 1);//把栈顶返回值出栈,清理堆栈
}
lua_pop(lua, 1);//把压入的错误处理函数出栈
printf("top is %d\n", lua_gettop(lua));

编写lua代码

function event(e,obj)
    print("c++ call lua function");
    print(e)
    print(obj.name)
    --return "return lua event"    --返回普通返回值
    return {id=123}    --返回一个表
end

--定义一个错误函数,在lua遇到错误时在c++中调用
function ferror(e)
    print("my error:" .. e);
    return "lua change error"
end

六、C++与Lua结合示例(mfc程序)

七、注意

  1. 获取table的长度

对于一个table来说,要获得其长度,最简单的应该是直接使用“#”操作符。

注意table.getn函数在lua5.2之后版本已经被移除。

当然,如果table是一个数组,那么长度计算一般是正确的(没有值为nil)

t={1,2,3,4}
print(#t)
--this will output 4

虽然“#”操作符简单,但是,其计数有时候并不准确。

t={[-1]=-1,[0]=0,[1]=1,[2]=2,[4]=5}
print(#t)
--this will output 4
t={[-1]=-1,[2]=1}
print(#t)
--this will output 0
t={a=1,b=2}
print(#t)
--this will output 0

所以,如果我们计算table,最好自己写一个函数

function table_leng(t)
  local leng=0
  for k, v in pairs(t) do
    leng=leng+1
  end
  return leng;
end

t={2,3,4,5}
t[2]=nil
print(#t) --this will output 4
print(table_leng(t)) --this will output 3, because t[2] was deleted
  1. 检查是否堆栈泄露

压栈和出栈操作时要留意是否存在漏了出栈导致堆栈泄露

在操作前和操作后都检查栈的数量:int (lua_gettop) (lua_State *L);

printf("top is %d\n", lua_gettop(lua)); //返回此时栈的数量 输出:top is 4

你可能感兴趣的:(c++,lua,lua,开发语言)