Lua 教程 - 第十章:Lua 与 C 交互
第十章:Lua 与 C 交互
Lua 的设计目标之一就是作为一种嵌入式语言,方便地与 C/C++ 等宿主语言进行交互。Lua 提供了丰富的 C API,允许我们在 C 代码中操作 Lua 状态机、调用 Lua 函数、读写 Lua 变量,反之亦然。
本章将介绍 Lua C API 的基础知识,包括栈操作、调用 Lua 函数以及在 C 中编写可供 Lua 调用的函数。
10.1 Lua C API 基础:虚拟栈
Lua 与 C 之间的数据交换是通过一个抽象的栈(Stack)来进行的。
-
Lua 将数据压入栈中,C 从栈中取出数据。
-
C 将数据压入栈中,Lua 从栈中取出数据。
栈索引
栈中的每个元素都有一个索引。
-
正索引:从栈底开始,1 表示栈底第一个元素,2 表示第二个,依此类推。
-
负索引:从栈顶开始,-1 表示栈顶元素,-2 表示栈顶下面的那个,依此类推。
常用栈操作 API
-
lua_pushnil(L): 压入 nil。 -
lua_pushboolean(L, b): 压入布尔值。 -
lua_pushnumber(L, n): 压入数字。 -
lua_pushstring(L, s): 压入字符串。 -
lua_toboolean(L, index): 将栈中指定索引的元素转换为 C 布尔值。 -
lua_tonumber(L, index): 转换为 C 数字。 -
lua_tostring(L, index): 转换为 C 字符串。 -
lua_gettop(L): 返回栈顶索引(即栈中元素个数)。 -
lua_settop(L, index): 设置栈顶。 -
lua_pop(L, n): 从栈顶弹出 n 个元素。
10.2 在 C 中调用 Lua (嵌入 Lua)
要在 C 程序中运行 Lua 代码,我们需要创建一个 Lua 状态机(lua_State)。
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
int main(void) {
// 1. 创建 Lua 状态机
lua_State *L = luaL_newstate();
// 2. 打开标准库
luaL_openlibs(L);
// 3. 运行 Lua 代码
if (luaL_dostring(L, "print('Hello from C!')")) {
printf("Error: %s\n", lua_tostring(L, -1));
}
// 4. 获取全局变量
lua_getglobal(L, "print"); // 压入 print 函数
lua_pushstring(L, "Calling Lua function from C"); // 压入参数
// 5. 调用函数: lua_pcall(L, 参数个数, 返回值个数, 错误处理函数索引)
if (lua_pcall(L, 1, 0, 0) != LUA_OK) {
printf("Error calling function: %s\n", lua_tostring(L, -1));
}
// 6. 关闭状态机
lua_close(L);
return 0;
}
编译命令(Linux):
gcc main.c -o main -llua -lm -ldl
10.3 在 Lua 中调用 C (扩展 Lua)
我们可以编写 C 函数,注册到 Lua 中,然后在 Lua 脚本里像调用普通函数一样调用它。
所有注册给 Lua 的 C 函数必须符合统一的原型:
typedef int (*lua_CFunction) (lua_State *L);
函数返回一个整数,表示压入栈的返回值个数。
示例:编写一个 C 函数求和
#include <lua.h>
#include <lauxlib.h>
// 实现求和函数
static int l_sum(lua_State *L) {
int n = lua_gettop(L); // 获取参数个数
double sum = 0;
for (int i = 1; i <= n; i++) {
if (!lua_isnumber(L, i)) {
lua_pushstring(L, "incorrect argument");
lua_error(L);
}
sum += lua_tonumber(L, i);
}
lua_pushnumber(L, sum); // 压入结果
return 1; // 返回值个数为 1
}
// 注册函数库
static const struct luaL_Reg mylib[] = {
{"sum", l_sum},
{NULL, NULL}
};
// 模块入口函数 (luaopen_模块名)
int luaopen_mylib(lua_State *L) {
luaL_newlib(L, mylib);
return 1;
}
编译成动态库(Linux):
gcc -shared -fPIC -o mylib.so mylib.c -llua
在 Lua 中使用:
local mylib = require("mylib")
print(mylib.sum(10, 20, 30)) -- 60.0
10.4 Userdata (用户数据)
userdata 允许 C 在 Lua 变量中存储自定义的数据结构(如结构体指针、文件句柄等)。
-
Full userdata: Lua 管理内存,C 可以通过 metatable 定义其行为(如 GC 回收时的操作)。
-
Light userdata: 仅仅是一个 C 指针,Lua 不管理其生命周期。
// 创建 userdata
MyStruct *p = (MyStruct *)lua_newuserdata(L, sizeof(MyStruct));
10.5 总结
Lua 的 C API 极其强大,它使得 Lua 可以:
-
作为配置语言: C 程序读取 Lua 配置文件。
-
作为脚本插件: C 程序提供钩子,调用 Lua 脚本扩展功能。
-
高性能计算: Lua 负责逻辑,C 负责密集计算。
练习题
-
编写一个 C 程序,读取 Lua 配置文件
config.lua中的width和height变量(整数),并打印出来。 -
编写一个 C 扩展库,提供一个函数
reverse(s),用于反转字符串(虽然 Lua 也能做,但请尝试用 C 实现并暴露给 Lua)。 -
阅读 Lua 官方文档中关于
luaL_check*系列函数(如luaL_checknumber)的说明,并在你的 C 扩展中使用它们来增强参数检查。
下一章预告:我们将通过几个实战项目(如简单的解释器、文本处理工具等)来综合运用前面所学的知识,巩固你的 Lua 编程技能。

