第十章: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 可以:

  1. 作为配置语言: C 程序读取 Lua 配置文件。

  2. 作为脚本插件: C 程序提供钩子,调用 Lua 脚本扩展功能。

  3. 高性能计算: Lua 负责逻辑,C 负责密集计算。

练习题

  1. 编写一个 C 程序,读取 Lua 配置文件 config.lua 中的 widthheight 变量(整数),并打印出来。

  2. 编写一个 C 扩展库,提供一个函数 reverse(s),用于反转字符串(虽然 Lua 也能做,但请尝试用 C 实现并暴露给 Lua)。

  3. 阅读 Lua 官方文档中关于 luaL_check* 系列函数(如 luaL_checknumber)的说明,并在你的 C 扩展中使用它们来增强参数检查。


下一章预告:我们将通过几个实战项目(如简单的解释器、文本处理工具等)来综合运用前面所学的知识,巩固你的 Lua 编程技能。