Lua 调用 C++ 函数

零、前言

从前面分享的 "C++ 调用 Lua 函数" 文章知道,C/C++ 调用 Lua 函数时,是需要按照规则,将参数和函数压入栈中,然后通过 lua_pcalllua_call 调用,最终再通过栈获取 Lua 返回的值。

同样 "Lua 调用 C++ 函数" 也需要遵循一定规则,而不是所有的 C/C++ 函数均可以进行调用。需要将 C/C++ 函数进行注册,将函数地址提供给 Lua 进行调用。交互过程同样使用了与 C/C++ 调用 Lua 函数时相同类型的栈,C/C++ 函数从栈中获取参数,并将结果压入栈中进行返回给 Lua 函数。

值得注意:

"Lua 调用 C/C++ 函数" 时,每次函数调用都有自己私有的局部栈,这不是一个全局栈。函数入参的第一个参数总位于局部栈中索引为 1 的位置,即栈底。

即使是 C/C++ 调用 Lua 代码,然后 Lua 代码又调用 C/C++ 函数,也是遵循这一规则,"Lua 代码又调用 C/C++ 函数" 这一过程的栈,是本次调用的私有栈。

一、Lua 如何调用 C/C++ 函数

第一步,定义被 Lua 调用的函数,该函数需要遵循以下格式:

cpp 复制代码
// 所有要注册到 Lua 的函数都需要遵循这一格式
typedef int (*lua_CFunction) (lua_State *L);

这个函数需要一个指向 Lua 状态类型的指针作为参数。返回值为一个整数,表示 C/C++ 函数需要返回给 Lua 调用点多少个返回值,即被调用的 C/C++ 函数中真正压入栈的值个数。

这个函数在压入结果前不用先将栈进行清空,在函数返回后,Lua 会自动保存返回值并清空整个栈。

第二步,通过 lua_pushcfunction 注册函数。

cpp 复制代码
#define lua_pushcfunction(L,f)	lua_pushcclosure(L, (f), 0)

LUA_API void  (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);

lua_pushcfunction 会获取一个指向 C 函数的指针,然后在 Lua 中创建一个 "function" 类型,代表注册的函数。

第三步,将通过 lua_pushcfunction 压入栈的函数关联到 Lua 的变量中,这样 Lua 脚本才可以进行使用。

二、举个例子

我们编写一个获取目录下文件名的函数,例如使用我们的项目结构(如下图所示),当 Lua 中传入 "调用C++函数" 的路径时,会返回该文件夹下的所有文件名。

第一步,定义 C++ 函数。

l_dir 获取 Lua 传递的参数,会进行以下操作(可以结合代码的注释理解)

  1. 检测参数是否为字符串,如果不是会抛异常到 Lua
  2. 打开文件路径,检测文件是否存在,如果不存在则抛异常到 Lua
  3. 遍历文件夹下的文件,将其设置到 table 中
  4. 最后返回 1 ,表示只有一个返回值,即保存着文件名称的 table
cpp 复制代码
int l_dir(lua_State *L) {
    // 检测入参是否为字符串
    // 如果不是,则会抛出异常到 Lua 中,can't run config. file: .../6、Lua回调C++/调用C++函数/读取目录函数.lua:11: bad argument #1 to 'dir' (string expected, got table)
    const char *path = luaL_checkstring(L, 1);

    // 打开相应目录
    DIR *dir = opendir(path);
    if (dir == nullptr) {
        // 抛异常至 Lua
        luaL_error(L, strerror(errno));
    }

    // 创建表,用于装载目录下 "索引" 和 "文件名"
    // 格式为 table[index] = "文件名"
    lua_newtable(L);
    int i = 1;
    struct dirent *entry;
    while ((entry = readdir(dir)) != nullptr) {
        // 压入 key ,即索引
        lua_pushinteger(L, i++);
        // 压入 value ,即文件名
        lua_pushstring(L, entry->d_name);
        // 将 key 和 value 插入 table
        lua_settable(L, -3);
    }

    closedir(dir);
    // 只有一个返回值,即 table
    return 1;
}

第二步,通过 lua_pushcfunction 注册函数,并且将其与 Lua 的变量关联,最后运行 Lua 文件。

cpp 复制代码
// 压入 l_dir 函数
lua_pushcfunction(L, l_dir);
// 将压入的函数 l_dir 设置为 dir 变量
lua_setglobal(L, "dir");

std::string fname2 = PROJECT_PATH + "/6、Lua回调C++/调用C++函数/读取目录函数.lua";
if (luaL_loadfile(L, fname2.c_str()) || lua_pcall(L, 0, 0, 0)) {
    printf("can't run config. file: %s\n", lua_tostring(L, -1));
}

第三步,Lua 文件中进行调用 dir 函数

lua 复制代码
print("dir: ", dir);

local currentPath = debug.getinfo(1, "S").source:sub(2):match("(.*/)")

print("--------------- 传递错误参数 ---------------")
print(pcall(function()
    dir({ "传递错误参数" })
end))

print("--------------- 存在目录 ---------------")
local dirTable
local isSuccess, msg = pcall(function()
    dirTable = dir(currentPath)
end)
if isSuccess then
    for key, value in pairs(dirTable) do
        io.write(key, value, "\n")
    end
else
    print("打开目录失败", currentPath, msg)
end

print("------------------------------")

print("--------------- 不存在目录 ---------------")
isSuccess, msg = pcall(function()
    dirTable = dir("不存在目录")
end)
if isSuccess then
    for key, value in pairs(dirTable) do
        io.write(key, value, "\n")
    end
else
    print("打开目录失败", currentPath, msg)
end
print("------------------------------")

会输出下面的内容

lua 复制代码
dir: 	function: 0x1058abe10
--------------- 传递错误参数 ---------------
false	.../6、Lua回调C++/调用C++函数/读取目录函数.lua:13: bad argument #1 to 'dir' (string expected, got table)
--------------- 存在目录 ---------------
1.
2..
3读取目录函数.h
4简易的sin函数.lua
5读取目录函数.lua
6简易的sin函数.h
7读取目录函数.cpp
8简易的sin函数.cpp
------------------------------
--------------- 不存在目录 ---------------
打开目录失败	/Users/jiangpengyong/Desktop/code/Lua/Lua_CPP_2022/6、Lua回调C++/调用C++函数/	.../6、Lua回调C++/调用C++函数/读取目录函数.lua:33: No such file or directory
------------------------------

这样就达到了将 C/C++ 函数暴露至 Lua 中,Lua 文件在需要的时候进行调用函数并获取相应的返回值了。

三、写在最后

Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)

如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀

公众号搜索 "江澎涌",更多优质文章会第一时间分享与你。

相关推荐
zaim144 分钟前
计算机的错误计算(一百一十四)
java·c++·python·rust·go·c·多项式
学习使我变快乐1 小时前
C++:const成员
开发语言·c++
一律清风4 小时前
QT-文件创建时间修改器
c++·qt
风清扬_jd4 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
Death2005 小时前
Qt 6 相比 Qt 5 的主要提升与更新
开发语言·c++·qt·交互·数据可视化
麻辣韭菜7 小时前
网络基础 【HTTP】
网络·c++·http
阿史大杯茶7 小时前
Codeforces Round 976 (Div. 2 ABCDE题)视频讲解
数据结构·c++·算法
转调8 小时前
每日一练:地下城游戏
开发语言·c++·算法·leetcode
不穿格子衬衫8 小时前
常用排序算法(下)
c语言·开发语言·数据结构·算法·排序算法·八大排序
wdxylb8 小时前
使用C++的OpenSSL 库实现 AES 加密和解密文件
开发语言·c++·算法