零、前言
从前面分享的 “C++ 调用 Lua 函数” 文章知道,C/C++ 调用 Lua 函数时,是需要按照规则,将参数和函数压入栈中,然后通过 lua_pcall
或 lua_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 调用的函数,该函数需要遵循以下格式:
// 所有要注册到 Lua 的函数都需要遵循这一格式
typedef int (*lua_CFunction) (lua_State *L);
这个函数需要一个指向 Lua 状态类型的指针作为参数。返回值为一个整数,表示 C/C++ 函数需要返回给 Lua 调用点多少个返回值,即被调用的 C/C++ 函数中真正压入栈的值个数。
这个函数在压入结果前不用先将栈进行清空,在函数返回后,Lua 会自动保存返回值并清空整个栈。
第二步,通过 lua_pushcfunction
注册函数。
#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 传递的参数,会进行以下操作(可以结合代码的注释理解)
- 检测参数是否为字符串,如果不是会抛异常到 Lua
- 打开文件路径,检测文件是否存在,如果不存在则抛异常到 Lua
- 遍历文件夹下的文件,将其设置到 table 中
- 最后返回 1 ,表示只有一个返回值,即保存着文件名称的 table
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 文件。
// 压入 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 函数
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("------------------------------")
会输出下面的内容
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吧,码字不易,请多多支持)
如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀。
公众号搜索 “江澎涌”,更多优质文章会第一时间分享与你。