• C++与Lua交互之配置&交互原理&示例


    |Lua 简介

    Lua 是一种轻量小巧的脚本语言,也是号称性能最高的脚本语言,它用C语言编写并以源代码形式开放。

    某些程序常常需要修改内容,而修改的内容不仅仅是数据,更要修改很多函数的行为。

    而修改函数行为这种事,很难用简单的更改数据的方式来实现,若在源代码层面上改又得重新编译生成,导致修改成本高。

    而脚本语言先通过更改数据,并加了一层对数据解释成运行代码的步骤,从而使程序能在运行时更改复杂的函数行为而无需重新编译。

    它为程序大大地提供了灵活的扩展和定制功能,减少了修改的成本。

    而游戏程序往往会选择性能高的LUA作为脚本,来应对某些经常修改的模块。

    |编译、配置 Lua动态链接库

      (本文使用Lua-5.3.5版本)

    此外不建议编译配置Lua静态链接库,不然用到某些函数缺少dll会导致运行时错误

    Lua库C源码:https://www.lua.org/download.html

    下载lua-5.3.x.tar.gz文件,解压。

    创建DLL项目,选择Release模式

    将解压后的src文件夹下所有.h和.c文件(lua.c,luac.c和其他格式文件都不要)拖进项目,

    预处理器定义(宏定义)加上LUA_BULD_AS_DLL

    然后项目生成dll文件和lib文件,这两个文件就是编译好出来的动态链接库。

    最后,在自己的工程项目里,

    dll文件复制过来放在生成文件夹(第一次编译项目会在项目根目录生成的Debug/Release文件夹)里,

    lib文件复制过来放在项目里某个目录,

    那堆.h文件.c文件lua.c,luac.c和其他格式文件都不要)也要复制过来放在项目里某个目录,

    配置好项目的包含目录(放.h.c文件的那里)和库目录(放lib文件的那里)

    至此,项目配置Lua库完成

    然后可以在工程项目里如下代码包含lua库:

    #pragma comment(lib, "lua.lib")  
    extern "C"  
    {  
    #include <lua.h>  
    #include <lualib.h>  
    #include <lauxlib.h>  
    };

     

    |Lua 基本语法(部分)

    完整的语法教程->Lua编程参考文档:http://book.luaer.cn/

    部分Lua的基本变量类型:

    nil 无效值
    boolean 只有两个值:false和true
    number 双精度类型的实浮点数(Lua的数字类型只有双精度浮点数,并无整形单精度之分)
    string 字符串由一对双引号或单引号来表示
    function 由C或Lua编写的函数
    table

    Lua 中的表(table)其实是一个"关联数组",数组的索引可以是数字或者是字符串。

     

     

    Lua在定义一个变量时,无需声明它的类型:

    a = 12

    b = 250.520

    c = "hello world"

    d = {name = "asd",id = 2333}

    条件:

    if xxx then

      xxxx

    else

      xxxx

    end

    Lua的函数可以返还多个返还值

    函数格式:

    function xxx(xxxx)

    end

    |C与Lua的交互机制

    在用C/C++使用Lua库前,有必要理解它们的交互机制。

    C与Lua交互的基础是虚拟栈:

    (如图所示)

    此外,为了方便找到栈底栈顶元素的位置,这个虚拟栈还提供两种索引:

    正数索引和负数索引,从而使-1总是代表栈顶元素的索引,1总是代表栈底元素的索引

    交互基本原理:

    当C要调用Lua数据时,Lua把值压入栈中,C再从栈中取值;

    当Lua调用C数据时,C要将数据压入栈中,让Lua从栈中取值。

    交互值时大部分可以按上面的互相传输,但是交互函数稍微更复杂:

    当C要调用Lua函数时,Lua先将Lua函数压入栈中,C再将数据(作为参数)继续压入栈中,

    然后用API调用栈上的lua函数+参数,调用完后,Lua函数和参数都会出栈,而函数计算后的返还值会压入栈中。

    当Lua要调用C函数时,需要通过API注册符合lua规范的C函数,来让Lua知道该C函数的定义。

    |C/C++调用Lua脚本

    先编写一个测试用的Lua脚本文件,

    (由于博主新装电脑,暂时直接用记事本编辑,但是没语法检查容易出错,这里推荐使用其它专业的lua编辑器,例如vsc,lua studio等)

    打开lua脚本文件:

    char lua_filename[] = "test.lua";
    lua_State *L = load_lua(lua_filename);
    if (NULL == L) {
      return -1;
    }

    读取lua文件的一般变量:

    lua_getglobal(L, "str");
    printf("str:%s
    ",lua_tostring(L, -1));
    lua_getglobal(L, "number");
    printf("number:%f
    ", lua_tonumber(L, -1));

    读取lua文件的table里的变量:

    lua_getglobal(L, "table");
    //记录table的索引
    int tableIndex = lua_gettop(L);
    
    //对-1位置的table取name变量压入栈顶
    lua_getfield(L, -1, "name");
    printf("table:name:%s
    ",lua_tostring(L, -1));
    
    //对tableIndex位置的table取table2变量压入栈顶
    lua_getfield(L, tableIndex, "table2");
    //对-1位置的table2取name2变量压入栈顶
    lua_getfield(L, -1, "name2");
    printf("table:table2:name2:%s
    ", lua_tostring(L, -1));

    读取lua文件的函数,并调用之:

    lua_getglobal(L, "add");//读取函数到栈顶
    lua_pushnumber(L, 10); //压入参数 10
    lua_pushnumber(L, 20); //压入参数 20
    //调用函数,若失败返还非0
    //lua_pcall第二个参数是指参数的数量,第三个参数是指返还值的数量
    if (lua_pcall(L, 2, 1, 0) != 0) {
        printf("lua_pcall failed: %s
    ", lua_tostring(L, -1));
        return -1;
    }
    //读取目前栈顶的元素,也就是返还值
    double result = lua_tonumber(L, -1);
    printf("add result:%f
    ",result);

     

    执行上述代码,我们便能看到如下结果

    调用lua API简单总结:

    将Lua脚本里的变量压入栈中

    //根据name获取某个全局变量,压入栈顶
    int lua_getglobal(lua_State *L, const char *name);
    //根据name获取index索引的table元素里的某个变量,压入栈顶
    int lua_getfield(lua_State *L, int index, const char *name);

    将C变量压入栈中

    //将数字压入栈顶
    void lua_pushnumber(lua_State *L,double number);
    //将字符串压入栈顶
    const char *lua_pushstring(lua_State *L, const char *str);

    将栈中某个位置的元素提取成C变量

    //将index索引的元素以数字的形式提取
    double lua_tonumber(lua_State *L, int index);
    //将index索引的元素以字符串的形式提取,返还
    const char* lua_tostring(lua_State *L, int index);

    利用栈调用lua函数

    //调用lua函数,arguNum是参数的个数,returnNum是返还值的个数,errorHandleIndex是函数调用错误时会另外调用的错误处理函数的索引(0视为无)
    //调用前要求:依次压入 lua函数元素,第1个参数元素,第2个参数元素....
    //调用后:调用的lua函数元素和所有参数元素 会在栈里被清理掉,并且若干个返还值元素将压入栈顶
    int lua_pcall(lua_State *L, int arguNum, int returnNum, int errorHandleIndex);

     

    |Lua脚本调用C函数

    首先编写好要调用的C函数,

    但是这个C函数并不会像我们往常编写的“正宗C函数”。

    首先该函数格式应为:

    static int xxxxx(lua_State *L) {
      //balabala随便做点事什么
        return 一个数字;
    }

    xxxxx的返还值 代表 注册后该函数返还值的个数

    那如何接受参数呢?这得通过上面介绍过的“将栈中某个位置的元素提取成C变量”方法获取参数。

    那如何返还返还值呢?同样通过"将C变量压入栈中"方法将返还值压入栈顶。

    例如1个参数、无返还值的print_num函数

    static int print_num(lua_State *L) {
        double a = lua_tonumber(L, -1);
        printf("This num is %f", a);
    return 0; }

    有3个参数、1个返还值的add_three函数

    static int add_three(lua_State *L) {
        int a = lua_tonumber(L, -1);
    int b = lua_tonumber(L, -2);
    int c = lua_tonumber(L, -3);
    int sum = a + b + c; lua_pushnumber(L, sum); return 1; }

    然后我们在代码里用API将上述C函数注册到Lua环境里:

    第二个参数为在Lua脚本里要注册的函数名字,第三个参数为要注册的C函数指针

    lua_register(L, "print1", print_num);
    lua_register(L, "add3", add_three);

     接下来修改脚本文件内容:

    我们看看在C调用Lua脚本的callCFunc(),这个函数里面能不能正确调用回2个注册的C函数。

     1     char lua_filename[] = "test.lua";
     2     lua_State *L = load_lua(lua_filename);
     3     if (NULL == L) {
     4         return -1;
     5     }
     6     // 注册函数
     7     lua_register(L, "print1", print_num);
     8     lua_register(L, "add3", add_three);
     9 
    10     //调用Lua脚本的callCFunc函数
    11     lua_getglobal(L, "callCFunc");
    12     lua_pcall(L, 0, 0, 0);

    结果如我们所料:

    通过上面C与Lua的交互,我们发现它们的交互机制并不简单,

    而且尚不支持C++这种更复杂的更多特性与Lua交互(类/对象/等)。

    实际工程中我们往往不想将注意力放在交互的底层过程,而是想如何方便的直接使用交互。

    于是可以使用github现有的库以已达到C++与Lua方便交互的作用。

    |使用Kaguya C++ binding库

    kaguya c++ binding下载地址:https://github.com/satoren/kaguya

    kaguya是一个很易用的库,它github的使用说明也十分浅显易懂,就连它的配置也是十分简单的:

    首先确保你的项目已经包含了lua5.1~lua5.3的环境,

    然后只需在你的项目添加"kaguya/include"目录到项目的"头文件包含目录"即可。

    本文就只简单示范它的几个用法(因为github的说明足够详细了,可自行查阅):

    testkaguya.lua文件:

    str = "Im dont know what to write"
    number = 250.520
    
    table = {
    name = "Ezio",
    id = 123456,
    table2 = {name2 = "Auditore",id2 = 23333}
    }
    
    function useCppClass(obj)
        obj:a()
    end

    C++测试用代码:

     1 #include <iostream>
     2 #include <string> 
     3 #include "kaguya/kaguya.hpp"
     4 
     5 using namespace std;
     6 
     7 class Base {
     8 private:
     9     int shit;
    10 public:
    11     virtual void a() {cout << "Base::a()";}
    12 };
    13 
    14 class Derived : public Base {
    15 public:
    16     virtual void a() { cout << "Derived::b()"; }
    17 };
    18 
    19 int main()
    20 {
    21     //----初始化-----//
    22     kaguya::State state;
    23     state.dofile("testkaguya.lua");
    24 
    25     //-----执行lua代码-----//
    26     state("number2 = 233");
    27     state("str2 = 'ok'");
    28 
    29     //-----值交互----//
    30     std::string value0 = state["str"];
    31     cout << value0 << endl << endl;
    32 
    33     std::string value1 = state["str2"];
    34     cout << value1 << endl << endl;
    35 
    36     double value2 = state["number"];
    37     cout << value2 << endl << endl;
    38 
    39     std::string value3 = state["table"]["name"];
    40     cout << value3 << endl << endl;
    41 
    42     state["tbl"] = kaguya::NewTable();
    43     state["tbl"]["value"] = 1;
    44     double value4 = state["tbl"]["value"];
    45     cout << value4 << endl << endl;
    46 
    47     //-----函数交互-----//
    48     int funcReturn1 = state["math"]["abs"](-32);
    49     assert(funcReturn1 == 32);
    50 
    51     auto funcReturn2 = state["math"]["abs"].call<int>(-32);
    52     assert(funcReturn2 == 32);
    53     //-----类部分-----//
    54     state["Base"].setClass(kaguya::UserdataMetatable<Base>()
    55         .addFunction("a", &Base::a)
    56     );
    57     state["Derived"].setClass(kaguya::UserdataMetatable<Derived, Base>()
    58         .addFunction("a", &Derived::a)
    59     );
    60 
    61     Base obj1;
    62     Derived obj2;
    63     state["useCppClass"](obj1);
    64     state["useCppClass"](obj2);
    65 
    66     system("pause");
    67     return 0;
    68 }

    测试结果:

  • 相关阅读:
    [08] 包装器类
    [07] String字符串
    [06] Java的数据类型
    [05] 利用private来封装
    [04] 包和访问权限修饰符
    [03] 类的结构和创建对象
    [02] 类和对象
    [01] Java语言的基本认识
    通过Excel认识POI
    浅谈SQL优化入门:3、利用索引
  • 原文地址:https://www.cnblogs.com/KillerAery/p/9249145.html
Copyright © 2020-2023  润新知