Lua与C++交互:函数

Lua虚拟栈

Lua和C++交互,必须通过Lua虚拟栈,所以首先要理解Lua虚拟栈。
栈的特点是先进后出,在Lua中,Lua堆栈是一个struct,它的索引可以是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶,lua的栈是在lua_State的时候创建的。
这里写图片描述

  1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.

  2. 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.使用使用静态依赖的方式

  1. 新建一个空的win32控制台工程,记得在vc++目录中,把lua中的头文件和lib文件的目录包含进来,然后->链接器->附加依赖项->将lua51.lib和lua5.1.lib也包含进来。
  2. 在目录下新建一个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栈以后,该栈会自动被清空。

猜你喜欢

转载自blog.csdn.net/s17728022507/article/details/75000192
今日推荐