【lua】使用 tolua 实现 lua 与 c++ 交互

tolua 简介

tolua 也叫 tolua++,是一个第三方库,简化了 lua 访问 c/c++ 的工作。tolua 自动导出 c/c++ 中的常量、变量、函数、类等,提供给 lua 直接访问和调用。lua 与 c 是可以直接相互访问的,不使用 tolua 或其它第三方库也可以实现 lua 调用 c/c++,但比较麻烦,开发效率太低。

lua 直接访问 c/c++ 可以参考我这篇文章
lua 访问 c

可以看到无论是在 c 程序中创建一个 lua 环境来执行 lua 代码,还是导出一个 c 模块供 lua 程序使用;要让 lua 能访问 c 函数,必须将手动将 c 函数注册到 lua 环境中;另外,c 函数参数和返回值都需要手动操作栈。这个效率是很慢的,最好的办法就是像写普通 c 代码一样,然后将 c 程序导出成 lua 可以直接调用的代码,tolua 就是干这个事的。

tolua 官网:http://webserver2.tecgraf.puc-rio.br/~celes/tolua/tolua-3.2.html#using

tolua 下载地址:https://github.com/LuaDist/tolua

使用 tolua

从 github 下载的 tolua 是源代码,必须先编译,编译之后我们将得到两个文件 tolua.exe 和 tolua.lib。tolua.exe 更像一个工具,负责从包文件(.pkg) 中生成 .c 文件或 .cpp 文件,这个 c/c++ 源文件就是导出的可供 lua 调用的代码。tolua.lib 则是 tolua 源码编译后的库,要在程序中使用 tolua,则必须包括这个库文件。

tolua 的编译使用 cmake 来编译,打开 cmake gui,然后选择 tolua 的根目录,再选择生成项目的存放路径,然后点击 Configure,选择要使用的 ide(windows 上一般为 visual studio),然后点击 Generate 就会生成相应的项目文件。具体的就不详说了,网上有很多教程。生成项目之后直接打开 vs 解决方案,生成所有项目即可。

编译完成之后,共有下面几个文件
* tolua.exe 用于从 .pkg 文件生成 .c/.cpp 文件
* tolua.dll 动态链接库文件
* tolua.lib 动态链接库对应的 lib 文件,如果是使用静态链接库项目生成的 lib 文件,则只有一个 lib 文件,没有 dll 文件
* tolua.h 头文件,在源代码的 include 目录下

使用 tolua 有三步,

第一步,正常编写 c/c++ 代码,然后编写对应的 package 文件,package 文件语法基本与 .h 文件一样,注意下面几点即可
* 不能有 public,private 等作用域修饰符,只能导出共有的成员,默认就是 public
* 函数原型和头文件保持一致,包括函数名参数和返回值
* 虚函数不能导出
* 在文件头使用 $#include "test.h" 包含头文件

第二步,使用 tolua.exe 从 package 文件生成相应的 c/c++ 源文件。在 tolua.exe 所有目录打开命令行,然后输入 tolua -n [mylib] -o [test.cpp] test.pkg,-n 参数指明这个文件所在的包,如果不写的话就默认跟文件名一样,-o 指明生成的源文件名,这个参数是必须的,最后就是源 package 文件。因为我生成的 tolua.exe 是依赖于动态链接库的,所以要把 tolua.dll 放在 tolua.exe 目录下,如果是使用静态链接库生成的 tolua.exe 则不用。为了方便起见,也可以将 tolua.exe 添加在环境变量,把 tolua.exe 所在的路径添加到 path 变量中去即可。

第三步,在项目中使用 tolua 生成的文件,这里的文件包括使用 c/c++ 写的原始代码,使用 tolua 导出的 c/c++ 源文件。导出的源文件中有一个方法 tolua_mylib_open,其中 mylib 就是上面 -n 参数指定的包名。使用之前要先在 c++ 层创建 lua 环境之后调用 tolua_mylib_open 函数,之后 lua 代码就可以访问 c++ 的内容了,访问的时候是通过 tolua 调用到上面导出的源文件,然后再调用到原始的实现文件。

导出源文件

上面讲了使用 tolua 的三部曲,下面使用实际例子讲解一下。首先,正常编写 c++ 代码,这里我们创建一个类

原始头文件

//mylib.h
class Test
{
public:
    Test(int a, int b);
    ~Test();
    void sayHello();
    int add();
    int getA();

private:
    int a;
    int b;
};

原始源文件

//mylib.cpp
#include "mylib.h"
#include <iostream>

Test::Test(int a, int b)
{
    this->a = a;
    this->b = b;
}

Test::~Test()
{
}

void Test::sayHello()
{
    std::cout << "hello world" << std::endl;
}

int Test::add()
{
    return this->a + this->b;
}

int Test::getA()
{
    return this->a;
}

package 文件

$#include "mylib.h"
class Test
{
    Test(int, int);
    ~Test();
    void sayHello();
    int add();  
    int getA();
};

可以看到 package 文件和 c++ 头文件基本一致,要注意的是要在文件头引入头文件,然后把 public 关键字去掉。所有公有的函数或数据都可以导出,如果不想导出某个函数,则在 package 文件中不要定义就可以了。

然后打开命令行,输入下面的命令

tolua -n mylib -o tolua.cpp mylib.pkg

导出的文件名可以任意命名,但因为我们已经有一个原始的源文件 mylib.cpp 了,所以这里不能将导出的源文件命名为 mylib.cpp,否则后面使用的时候就会有问题,这里我命名为 tolua.cpp。还有一个要注意的就是必须把 mylib.h 跟 mylib.pkg 放在一起,因为 package 文件需要用到头文件(第一行就已经引入头文件了)

现在我们有了 mylib.h、mylib.cpp 和 tolua.cpp 这三个文件,接下来就可以在项目中使用了

在 c++ 程序中使用 tolua

新建一个 c++ 控制台程序,因为要使用到 lua 和 tolua,所以要把相应的头文件和库文件包含进来,头文件有 lua.h、lualib.h、lauxlib.h、lua.hpp、luaconf.h 和 tolua.h,库文件则有 lua.lib 和 tolua.lib。

首先,我们先来测试一下 lua 环境,新建一个 main.cpp 文件,输入下面代码

//main.cpp
#include <iostream>
extern "C"
{
#include "lualib.h"
#include "lauxlib.h"
}

using namespace std;

int main()
{
    int tolua_mylib_open(lua_State*);
    lua_State* state = luaL_newstate();
    luaL_openlibs(state);

    if (luaL_dostring(state, "print([[hello world]])") != 0)
    {
        cout << "excute lua file failed!" << endl;
    }
    lua_close(state);

    system("pause");
    return 0;
}

如果正确打印出 “hello world”,则说明 lua 环境没有问题,否则就检查一下头文件和库文件是否正确引入了

接下来把 mylib.h、mylib.cpp 和 tolua.cpp 这三个文件添加到项目中,我们先来分析下 tolua.cpp 这个文件的内容

static int tolua_collect_Test (lua_State* tolua_S){}

static void tolua_reg_types (lua_State* tolua_S){}

//对应构造函数
static int tolua_mylib_Test_new00(lua_State* tolua_S){}

//对应析构函数
static int tolua_mylib_Test_delete00(lua_State* tolua_S){}

//sayHello
static int tolua_mylib_Test_sayHello00(lua_State* tolua_S){}

//add
static int tolua_mylib_Test_add00(lua_State* tolua_S){}

//getA
static int tolua_mylib_Test_getA00(lua_State* tolua_S){}

LUALIB_API int luaopen_mylib (lua_State* tolua_S){}

TOLUA_API int tolua_mylib_open (lua_State* tolua_S){}

这里只列出主要的几个函数,可以看到 tolua 在导出 c++ 源码的时候为每个函数都生成一个对应的静态函数;除此之外,还有几个重要的函数,tolua_collect_Test 用于垃圾回收,tolua_reg_types 用于注册类名,toluaopen_mylib 用于打开库函数,tolua_mylib_open 用于打开 tolua,这是我们唯一关心的函数,在使用 tolua 之前必须先调用这个函数,这样所有导出的 c++ 函数就可以在 lua 中使用了

接下来开始测试在 lua 中访问 c++ 函数,新建一个 test.lua 文件,输入下面代码

local test = Test:new(1, 2)
test:sayHello()
print("a = " .. test:getA())
print("a + b = " .. test:add())

然后修改 main.cpp 文件

#include <iostream>
extern "C"
{
#include "lualib.h"
#include "lauxlib.h"
}
#include "mylib.h"

using namespace std;

int main()
{
    int tolua_mylib_open(lua_State*);
    lua_State* state = luaL_newstate();
    luaL_openlibs(state);
    tolua_mylib_open(state);


    if (luaL_dofile(state, "scripts/test.lua") != 0)
    {
        cout << "excute lua file failed!" << endl;
        lua_close(state);
        return 1;
    }
    lua_close(state);

    system("pause");
    return 0;
}

首先,要引入 mylib.h 头文件,然后调用 tolua_mylib_open(state); 打开 tolua,这个函数在 tolua.cpp 文件中定义和实现,main.cpp 并不知道这个函数,所以要在使用前手动定义一下 int tolua_mylib_open(lua_State*);,之后就可以执行 test.lua 文件了

如果没错误,将会看到下面的执行结果

tolua

在 lua 程序中使用 tolua

这种方式其实和在 c++ 程序中使用 tolua 没什么区别,只是我们测试的项目是 c++ 项目还是 lua 项目而已。要在纯 lua 程序中使用 tolua,首先得导出一个 c++ 模块。新建一个 c++ 动态链接库项目,同样地将 lua 和 tolua 的头文件和库文件包含进来,还有 mylib.h、mylib.cpp 和 tolua.cpp 这三个文件也添加进项目来。

导出 c 模块给 lua 使用同样可参考我这篇文章
lua 访问 c

导出 c++ 模块,在模块内注册一个函数,在这个函数里打开 tolua,然后 tolua 绑定的所有 c++ 函数就可以在 lua 中使用了。我们创建一个 lib.h 和 lib.cpp 文件,用于导出 c++ 模块

//lib.h
#pragma once
#include "lua.h"
extern "C"
{
    __declspec(dllexport) int luaopen_test(lua_State* L);
}
//lib.cpp
#include "mylib.h"
extern "C"
{
#include "lib.h"
#include "lualib.h"
#include "lauxlib.h"
}

static int export_tolua_test(lua_State* L)
{
    int tolua_mylib_open(lua_State* L);
    tolua_mylib_open(L);
    return 1;
}
static const luaL_Reg toluaTest[] =
{
    { "export_tolua_test",export_tolua_test },
    { NULL,NULL }
};

extern "C"
{
    __declspec(dllexport) int luaopen_test(lua_State* L)
    {
        luaL_newlibtable(L, toluaTest);
        luaL_setfuncs(L, toluaTest, 0);
        return 1;
    }
}

编译之后得到一个 lib.dll 文件,在 lua 代码中加载这个动态链接库文件,得到一个模块,这个模块里面有 export_tolua_test 这个函数,调用这个函数就会执行 tolua_mylib_open(L);,从而打开 tolua,则在 mylib 中导出的所有 c++ 东西都可以在 lua 中使用了

-- main.lua
-- 加载 c++ 模块
local test = require("test")
-- 调用 c++ 模块注册的函数,这个函数会打开 tolua
test.export_tolua_test()

-- 调用 tolua 导出的类、函数等
local t = Test:new(10, 20)
t:sayHello()
print(t:getA())
print(t:add())

使用 lua 解释器运行该代码,看到下面的执行结果

tolua

猜你喜欢

转载自blog.csdn.net/xingxinmanong/article/details/78137514