高性能混合编程:用脚本语言配合C++使用之Lua

Lua

Lua 与其他语言最大的不同点在于它的设计目标:不追求“大而全”,而是“小而美”。
Lua实现业务逻辑简单,可以配合在C++里使用,也可让C++配合Lua使用,C++可以实现底层和服务器一些高性能方面的应用,Lua可以实现一些复杂的业务逻辑,将两者配合使用,具体应用在不同的场景里,加快开发效率。

标准的 Lua(PUC-Rio Lua)使用解释器运行,速度虽然很快,但和 C/C++ 比起来还是有差距的。

使用LuaJIT代替标准Lua,效率还是很高的。
可以用 LuaJIT :https://luajit.org/ ,用了 JIT(Just in time)技术,能够把 Lua 代码即时编译成机器码,速度几乎可以媲美原生 C/C++ 代码。
缺点:更新较慢

建议:改用非官方的一个分支项目:OpenResty-LuaJIT
这个由 OpenResty 负责维护,非常活跃,修复了很多小错误。

git clone git@github.com:openresty/luajit2.git
make && make install

LuaBridge 是一个纯头文件的库,只要下载下来,把头文件拷贝到包含路径,就能够直接用:

git clone git@github.com:vinniefalco/LuaBridge.git

在Lua里调用C++

LuaJIT内置了一个 ffi 库(Foreign Function Interface),能够在 Lua 脚本里直接声明接口函数、直接调用,不需要任何的注册动作,更加简单方便。而且这种做法还越过了 Lua 传统的栈操作,速度也更快。

注意:只能识别纯 C 接口,不认识 C++
一定要将接口转换成C接口,内部可以用C++。

包含头文件的使用extern “C”


extern "C" {                // 使用纯C语言的对外接口
int num = 10; 
int my_add(int a, int b); 
}

int my_add(int a, int b)    // 一个简单的函数,供Lua调用
{
    return a + b;
}

然后就可以用 g++ 把它编译成动态库


g++ lua_shared.cpp -std=c++11 -shared -fPIC -o liblua_shared.so

然后Lua脚本
首先要用 ffi.cdef 声明要调用的接口,
再用 ffi.load 加载动态库,
这样就会把动态库所有的接口都引进 Lua,
然后就能随便使用了:


local ffi = require "ffi"            -- 加载ffi库
local ffi_load = ffi.load            -- 函数别名
local ffi_cdef = ffi.cdef

ffi_cdef[[                           // 声明C接口
int num;
int my_add(int a, int b);
]]

local shared = ffi_load("./liblua_shared.so")    -- 加载动态库 

print(shared.num)                                -- 调用C接口
local x = shared.my_add(1, 2)                    -- 调用C接口

在 ffi 的帮助下,让 Lua 调用 C 接口几乎是零工作量,但这并不能完全发挥出 Lua 的优势。

实际上,很少在Lua里调用C++,一般都是嵌入到别的语言宿主里使用。被宿主调用,然后再“回调”底层接口,利用它的“胶水语言”特性去粘合业务逻辑。

C++里调用Lua的使用方法:

方法一:
首先要调用函数 luaL_newstate(),创建出一个 Lua 虚拟机,所有的 Lua 功能都要在它上面执行。
然后,用完之后使用函数lua_close() 关闭。
可以使用RAII的方法写一个类来自动管理。
LuaBridge 没有对此封装,自己动手实现,借用课中例子:
用了智能指针 shared_ptr,在一个 lambda 表达式里创建虚拟机,顺便再打开 Lua 基本库:
1、创建Lua虚拟机


auto make_luavm = []()              // lambda表达式创建虚拟机
{
    std::shared_ptr<lua_State> vm(  // 智能指针
        luaL_newstate(), lua_close  // 创建虚拟机对象,设置删除函数
        );
    luaL_openlibs(vm.get());        // 打开Lua基本库

    return vm; 
};
#define L vm.get()                  // 获取原始指针,宏定义方便使用

在 LuaBridge 里,一切 Lua 数据都被封装成了 LuaRef 类,完全屏蔽了 Lua 底层那难以理解的栈操作。
它可以隐式或者显式地转换成对应的数字、字符串等基本类型,如果是表,就可以用“[]”访问成员,如果是函数,也可以直接传参调用,非常直观易懂。

注意:使用 LuaBridge 访问 Lua 数据时,只能用函数 getGlobal() 看到全局变量。想在 C++ 里调用 Lua 功能,就一定不能加“local”修饰。

2、调用上述函数创建了一个 Lua 虚拟机,然后获取了 Lua 内置的 package 模块,输出里面的默认搜索路径 path 和 cpath:


auto vm = make_luavm();                  // 创建Lua虚拟机
auto package = getGlobal(L, "package");  // 获取内置的package模块

string path  = package["path"];          // 默认的lua脚本搜索路径
string cpath = package["cpath"];         // 默认的动态库搜索路径

或者,直接调用luaL_dostring() 和 luaL_dofile() 直接执行 Lua 代码片段或者外部的脚本文件。
注意:luaL_dofile() 每次调用都会从磁盘载入文件,所以效率较低。
如果频繁调用,存入内存。存成一个字符串,再用 luaL_dostring() 运行:


luaL_dostring(L, "print('hello lua')");  // 执行Lua代码片段
luaL_dofile(L, "./embedded.lua");        // 执行外部的脚本文件

方法二:
提前在脚本里写好一些函数,加载后在 C++ 里逐个调用,这种方式比执行整个脚本更灵活。
具体做法:
先用 luaL_dostring() 或者 luaL_dofile() 加载脚本,然后调用 getGlobal() 从全局表里获得封装的 LuaRef 对象,就可以像普通函数一样执行了。


string chunk = R"(                    -- Lua代码片段
    function say(s)                   -- Lua函数1
        print(s)
    end
    function add(a, b)                -- Lua函数2
        return a + b
    end
)";

luaL_dostring(L, chunk.c_str());      // 执行Lua代码片段

auto f1 = getGlobal(L, "say");        // 获得Lua函数
f1("say something");                  // 执行Lua函数

auto f2 = getGlobal(L, "add");        // 获得Lua函数
auto v = f2(10, 20);                  // 执行Lua函数

只要掌握了上面的这些基本用法,并合理地划分出 C++ 与 Lua 的职责边界,就可以搭建出“LuaJIT + LuaBridge + C++”的高性能应用,运行效率与开发效率兼得。比如说用 C++ 写底层的框架、引擎,暴露出各种调用接口作为“业务零件”,再用灵活的 Lua 脚本去组合这些“零件”,写上层的业务逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值