用 lua 脚本配合 C++ 开发

作者:eastecho
2014-02-18
4 17 1

Lua 是一门脚本语言,它有着一个重要的特性就是:它很容易嵌入其它语言。现在,越来越多的 C++ 服务器和客户端融入了脚本的支持,尤其在网游领域,脚本语言已经渗透到了方方面面,比如你可以在你的客户端增加一个脚本,这个脚本将会帮你在界面上显示新的数据,亦或帮你完成某些任务,亦或帮你查看别的玩家或者 NPC 的状态。对于游戏测试来说,大量繁杂的工作都可以由 Lua 代劳,因此在测试领域它也是一把利器。

关于 Lua 的一些重要基础知识:

Lua 有重要之重要的概念,就是栈。Lua 与别的语言交互以及交换数据,是通过栈完成的。其实简单的解释一下,你可以把栈想象成一个箱子,你要给他数据,就要按顺序一个个的把数据放进去,当然,Lua 执行完毕,可能会有结果返回给你,那么 Lua 还会利用你的箱子,一个个的继续放下去。而你取出返回数据呢,要从箱子顶上取出,如果你想要获得你的输入参数呢?那也很简单,按照顶上返回数据的个数,再按顺序一个个的取出,就行了。不过这里提醒大家,关于栈的位置,永远是相对的,比如 -1 代表的是当前栈顶,-2 代表的是当前栈顶下一个数据的位置。栈是数据交换的地方,一定要有一些栈的概念。

首先、编译 Lua

首先下载 Lua 5.2.x(官网链接),一般情况下,很容易就可以通过 make macosx 或者 make linux 来编译。这样会生成 liblua.a 静态库文件,我们随后要连接这个库。

其次、准备环境

首先,我们要在开发工具中连接 liblua.a,然后要将 Lua 源代码文件的位置添加到编译器的头文件搜索目录列表里面去,这样我们的编译器才能找到 Lua 的头文件。

然后、创建程序

我们需要创建一个 C++ 的主程序,以便同 Lua 进行通信。

cpp 程序如下:

#include <iostream>
#include <lua.hpp>

extern "C" {
    static int l_cppfunction(lua_State *L) {
        double arg = luaL_checknumber(L,1);
        lua_pushnumber(L, arg * 0.5);
        return 1;
    }
}

using namespace std;

int main(int argc, const char * argv[])
{
    lua_State *L;
    L = luaL_newstate();
    cout << ">> 载入(可选)标准库,以便使用打印功能" << endl;
    luaL_openlibs(L);
    cout << ">> 载入文件,暂不执行" << endl;
    if (luaL_loadfile(L, "luascript.lua")) {
        cerr << "载入文件出现错误" << endl;
        cerr << lua_tostring(L, -1) << endl;
        lua_pop(L,1);
    }
    
    cout << ">> 从 C++ 写入数据 cppvar" << endl;
    lua_pushnumber(L, 1.1);
    lua_setglobal(L, "cppvar");
    
    cout << ">> 执行 lua 文件" << endl << endl;
    if (lua_pcall(L,0, LUA_MULTRET, 0)) {
        cerr << "执行过程中出现错误" << endl;
        cerr << lua_tostring(L, -1) << endl;
        lua_pop(L,1);
    }
    
    cout << ">> 从 Lua 读取全局变量 luavar 到 C++" << endl;
    lua_getglobal(L, "luavar");
    double luavar = lua_tonumber(L,-1);
    lua_pop(L,1);
    cout << "C++ 从 Lua 读取到的 luavar = " << luavar << endl << endl;
    
    cout << ">> 从 C++ 执行 Lua 的方法 myfunction" << endl;
    lua_getglobal(L, "myluafunction");
    lua_pushnumber(L, 5);
    lua_pcall(L, 1, 1, 0);
    cout << "函数返回值是:" << lua_tostring(L, -1) << endl << endl;
    lua_pop(L,1);
    
    cout << ">> 从 Lua 执行 C++ 的方法" << endl;
    cout << ">>>> 首先在 Lua 中注册 C++ 方法" << endl;
    lua_pushcfunction(L,l_cppfunction);
    lua_setglobal(L, "cppfunction");
    
    cout << ">>>> 调用 Lua 函数以执行 C++ 函数" << endl;
    lua_getglobal(L, "myfunction");
    lua_pushnumber(L, 5);
    lua_pcall(L, 1, 1, 0);
    cout << "函数返回值是:" << lua_tonumber(L, -1) << endl << endl;
    lua_pop(L,1);
    
    cout << ">> 释放 Lua 资源" << endl;
    lua_close(L);
    
    return 0;
}

其次,是 lua 文件,我们将它命名为 luascript.lua

print("Hello from Lua")
print("Lua code is capable of reading the value set from C++", cppvar)
luavar = cppvar * 3

function myluafunction(times)
  return string.rep("(-)", times)
end

function myfunction(arg)
  return cppfunction(arg)
end

运行 cpp 文件,结果如下:

>> 载入(可选)标准库,以便使用打印功能
>> 载入文件,暂不执行
>> 从 C++ 写入数据 cppvar
>> 执行 lua 文件

Hello from Lua
Lua code is capable of reading the value set from C++	1.1
>> 从 Lua 读取全局变量 luavar 到 C++
C++ 从 Lua 读取到的 luavar = 3.3

>> 从 C++ 执行 Lua 的方法 myfunction
函数返回值是:(-)(-)(-)(-)(-)

>> 从 Lua 执行 C++ 的方法
>>>> 首先在 Lua 中注册 C++ 方法
>>>> 调用 Lua 函数以执行 C++ 函数
函数返回值是:2.5

>> 释放 Lua 资源

下面做一下简要讲解:

初始化

lua_State *L;
L = luaL_newstate();
luaL_openlibs(L); // 载入(可选)标准库,以便使用打印功能

if (luaL_loadfile(L, "luascript.lua")) { // 载入文件,暂不执行
    cerr << "载入文件出现错误" << endl;
    cerr << lua_tostring(L, -1) << endl;
    lua_pop(L,1);
}

上述代码创建 lua_State 并载入标准库,同时载入代码 luascript.lua

从 C++ 向 Lua 添加变量

lua_pushnumber(L, 1.1);
lua_setglobal(L, "cppvar");

if (lua_pcall(L,0, LUA_MULTRET, 0)) {
    cerr << "执行过程中出现错误" << endl;
    cerr << lua_tostring(L, -1) << endl;
    lua_pop(L,1);
}

在 C++ 中通过 lua_setglobal 在 Lua 中设置一个全局变量 cppvar。因为 C++ 和 Lua 通过 lua_State 中的堆栈来交换数据,所以要先 push 数据到堆栈,然后调用 lua_setglobal,这样就将数据赋给相应的值。

设置完全局的 cppvar 之后,执行 lua_pcall 来运行我们的 Lua 代码文件,之后,Lua 就可以使用 cppvar 变量。在 Lua 代码中,还创建了新的全局变量 luavar 可以供 C++ 访问。

从 C++ 中读取 Lua 变量

lua_getglobal(L, "luavar");
double luavar = lua_tonumber(L,-1);
lua_pop(L,1);
cout << "C++ 从 Lua 读取到的 luavar = " << luavar << endl << endl;

要从 Lua 中读取数据,我们先要使用 lua_getglobal 将数据放到栈顶,然后将栈顶数据通过 lua_tonumber 转为 double,然后通过 lua_pop 将其移除出栈顶。

从 C++ 中调用 Lua 函数

lua_getglobal(L, "myluafunction");
lua_pushnumber(L, 5);
lua_pcall(L, 1, 1, 0);
cout << "函数返回值是:" << lua_tostring(L, -1) << endl << endl;
lua_pop(L,1);

由了前面的过程,这个比较容易理解了:首先我们通过 lua_getglobal 取得方法名称,即将其放到栈顶,然后通过 lua_pushnumber 给参数赋值,然后通过 lua_pcall 执行。

执行之后,再去栈顶获取返回结果,然后 lua_pop 将其移除。

从 Lua 中调用 C++ 函数

lua_pushcfunction(L,l_cppfunction);
lua_setglobal(L, "cppfunction");

lua_getglobal(L, "myfunction");
lua_pushnumber(L, 5);
lua_pcall(L, 1, 1, 0);
cout << "函数返回值是:" << lua_tonumber(L, -1) << endl << endl;
lua_pop(L,1);

这一段先是使用 lua_pushcfunction 来将 C++ 的方法 l_cppfunction 传递给 Lua,然后通过 lua_setglobal 给予其在 Lua 中的方法名称 cppfunction,接下来执行就很简单了。

释放

lua_close(L);

以上就是一个简单的 Lua 与 C++ 交互的简单例子,算是入门的基础吧。我们可以看到,C++ 和 Lua 能够很自由的进行通信,而我们也可以很方便的修改 Lua 文件代码来实现对 C++ 程序流程的直接控制,对游戏开发、调试有很大的好处。

近期点赞的会员

 分享这篇文章

eastecho 

从前的边城浪子,现在的路人乙 

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. 万有引力 2016-12-14

    好文章,顶一下

您需要登录或者注册后才能发表评论

登录/注册