<返回目录     Powered by claud/xia兄

第14课: C API集成

Lua C API基础

Lua C API工作原理:

Lua C API的核心是虚拟栈(Lua Stack),它作为Lua和C之间的数据交换桥梁。所有数据传递都通过栈进行:C函数从栈获取参数,将结果压入栈。栈是LIFO(后进先出)结构,索引从1开始,负数表示从栈顶开始的位置。这种设计使得C和Lua之间的交互类型安全且高效,同时简化了内存管理(Lua自动管理栈上对象的生命周期)。

// mylib.c - 简单的C扩展
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

// C函数:返回两个数的和
static int l_add(lua_State *L) {
    double a = luaL_checknumber(L, 1);  // 获取第一个参数
    double b = luaL_checknumber(L, 2);  // 获取第二个参数
    lua_pushnumber(L, a + b);           // 压入结果
    return 1;                            // 返回值数量
}

// 注册函数
static const struct luaL_Reg mylib[] = {
    {"add", l_add},
    {NULL, NULL}
};

// 模块入口
int luaopen_mylib(lua_State *L) {
    luaL_newlib(L, mylib);
    return 1;
}

// 编译: gcc -shared -fPIC -o mylib.so mylib.c -I/usr/include/lua5.3 -llua5.3

在Lua中使用C模块

-- 加载C模块
local mylib = require("mylib")

-- 调用C函数
local result = mylib.add(10, 20)
print(result)  -- 30

栈操作

// 栈是Lua和C交互的核心
static int stack_demo(lua_State *L) {
    // 压入值
    lua_pushnil(L);           // 压入nil
    lua_pushboolean(L, 1);    // 压入true
    lua_pushnumber(L, 3.14);  // 压入数字
    lua_pushstring(L, "hello"); // 压入字符串

    // 获取栈顶索引
    int top = lua_gettop(L);
    printf("栈中有 %d 个元素\n", top);

    // 访问栈元素(索引从1开始,负数从栈顶开始)
    double num = lua_tonumber(L, -2);  // 获取倒数第二个
    const char *str = lua_tostring(L, -1); // 获取栈顶

    // 弹出元素
    lua_pop(L, 2);  // 弹出2个元素

    return 0;
}

// 常用栈操作函数:
// lua_pushvalue(L, index)  - 复制指定索引的值到栈顶
// lua_remove(L, index)     - 删除指定索引的值
// lua_insert(L, index)     - 将栈顶值插入到指定位置
// lua_replace(L, index)    - 用栈顶值替换指定位置

类型检查和转换

static int type_demo(lua_State *L) {
    // 检查类型
    if (lua_isnumber(L, 1)) {
        printf("参数1是数字\n");
    }
    if (lua_isstring(L, 2)) {
        printf("参数2是字符串\n");
    }
    if (lua_istable(L, 3)) {
        printf("参数3是表\n");
    }

    // 强制类型检查(失败会报错)
    int num = luaL_checkinteger(L, 1);
    const char *str = luaL_checkstring(L, 2);

    // 可选参数
    int opt = luaL_optinteger(L, 3, 100);  // 默认值100

    // 类型转换
    double d = lua_tonumber(L, 1);
    const char *s = lua_tostring(L, 2);
    int b = lua_toboolean(L, 3);

    return 0;
}

表操作

static int table_demo(lua_State *L) {
    // 创建新表
    lua_newtable(L);

    // 设置字段: table["name"] = "张三"
    lua_pushstring(L, "张三");
    lua_setfield(L, -2, "name");

    // 设置数组元素: table[1] = 100
    lua_pushinteger(L, 100);
    lua_rawseti(L, -2, 1);

    // 读取字段
    lua_getfield(L, -1, "name");
    const char *name = lua_tostring(L, -1);
    lua_pop(L, 1);

    // 读取数组元素
    lua_rawgeti(L, -1, 1);
    int value = lua_tointeger(L, -1);
    lua_pop(L, 1);

    // 遍历表
    lua_pushnil(L);  // 第一个key
    while (lua_next(L, -2) != 0) {
        // 栈: ... table key value
        const char *key = lua_tostring(L, -2);
        const char *val = lua_tostring(L, -1);
        printf("%s = %s\n", key, val);
        lua_pop(L, 1);  // 弹出value,保留key
    }

    return 1;  // 返回表
}

调用Lua函数

// 从C调用Lua函数
void call_lua_function(lua_State *L) {
    // 假设全局有函数: function add(a, b) return a + b end

    lua_getglobal(L, "add");      // 获取函数
    lua_pushinteger(L, 10);       // 参数1
    lua_pushinteger(L, 20);       // 参数2

    // lua_pcall(L, nargs, nresults, errfunc)
    if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
        const char *err = lua_tostring(L, -1);
        printf("错误: %s\n", err);
        lua_pop(L, 1);
        return;
    }

    // 获取结果
    int result = lua_tointeger(L, -1);
    printf("结果: %d\n", result);
    lua_pop(L, 1);
}

用户数据(Userdata)

// 定义C结构体
typedef struct {
    int x;
    int y;
} Point;

// 创建userdata
static int point_new(lua_State *L) {
    int x = luaL_checkinteger(L, 1);
    int y = luaL_checkinteger(L, 2);

    // 分配userdata
    Point *p = (Point *)lua_newuserdata(L, sizeof(Point));
    p->x = x;
    p->y = y;

    // 设置元表
    luaL_getmetatable(L, "Point");
    lua_setmetatable(L, -2);

    return 1;
}

// 方法:获取距离
static int point_distance(lua_State *L) {
    Point *p = (Point *)luaL_checkudata(L, 1, "Point");
    double dist = sqrt(p->x * p->x + p->y * p->y);
    lua_pushnumber(L, dist);
    return 1;
}

// 垃圾回收
static int point_gc(lua_State *L) {
    Point *p = (Point *)luaL_checkudata(L, 1, "Point");
    printf("Point被回收: (%d, %d)\n", p->x, p->y);
    return 0;
}

// 注册Point类
int luaopen_point(lua_State *L) {
    // 创建元表
    luaL_newmetatable(L, "Point");

    // 设置__index指向自己
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");

    // 注册方法
    lua_pushcfunction(L, point_distance);
    lua_setfield(L, -2, "distance");

    // 注册__gc
    lua_pushcfunction(L, point_gc);
    lua_setfield(L, -2, "__gc");

    // 创建模块表
    lua_newtable(L);
    lua_pushcfunction(L, point_new);
    lua_setfield(L, -2, "new");

    return 1;
}

在Lua中使用Userdata

-- 使用Point类
local point = require("point")

local p = point.new(3, 4)
print(p:distance())  -- 5.0

错误处理

static int safe_divide(lua_State *L) {
    double a = luaL_checknumber(L, 1);
    double b = luaL_checknumber(L, 2);

    if (b == 0) {
        return luaL_error(L, "除数不能为0");
    }

    lua_pushnumber(L, a / b);
    return 1;
}

// 使用luaL_argcheck
static int check_demo(lua_State *L) {
    int age = luaL_checkinteger(L, 1);
    luaL_argcheck(L, age >= 0 && age <= 150, 1, "年龄范围无效");
    return 0;
}

注册全局函数

// 注册多个函数
static const struct luaL_Reg mathlib[] = {
    {"add", l_add},
    {"subtract", l_subtract},
    {"multiply", l_multiply},
    {"divide", l_divide},
    {NULL, NULL}
};

int luaopen_mathlib(lua_State *L) {
    // 创建模块表
    luaL_newlib(L, mathlib);

    // 添加常量
    lua_pushnumber(L, 3.14159);
    lua_setfield(L, -2, "PI");

    return 1;
}

嵌入Lua到C程序

// main.c - 在C程序中嵌入Lua
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

int main(void) {
    // 创建Lua状态机
    lua_State *L = luaL_newstate();

    // 加载标准库
    luaL_openlibs(L);

    // 执行Lua代码
    if (luaL_dostring(L, "print('Hello from Lua!')") != LUA_OK) {
        fprintf(stderr, "错误: %s\n", lua_tostring(L, -1));
        lua_close(L);
        return 1;
    }

    // 执行Lua文件
    if (luaL_dofile(L, "script.lua") != LUA_OK) {
        fprintf(stderr, "错误: %s\n", lua_tostring(L, -1));
    }

    // 设置全局变量
    lua_pushinteger(L, 42);
    lua_setglobal(L, "answer");

    // 获取全局变量
    lua_getglobal(L, "answer");
    int answer = lua_tointeger(L, -1);
    printf("answer = %d\n", answer);

    // 关闭Lua
    lua_close(L);
    return 0;
}

// 编译: gcc -o main main.c -llua5.3 -lm -ldl

完整示例:字符串处理库

// strlib.c
#include <lua.h>
#include <lauxlib.h>
#include <string.h>
#include <ctype.h>

// 转大写
static int str_upper(lua_State *L) {
    size_t len;
    const char *str = luaL_checklstring(L, 1, &len);

    char *result = malloc(len + 1);
    for (size_t i = 0; i < len; i++) {
        result[i] = toupper(str[i]);
    }
    result[len] = '\0';

    lua_pushstring(L, result);
    free(result);
    return 1;
}

// 反转字符串
static int str_reverse(lua_State *L) {
    size_t len;
    const char *str = luaL_checklstring(L, 1, &len);

    char *result = malloc(len + 1);
    for (size_t i = 0; i < len; i++) {
        result[i] = str[len - 1 - i];
    }
    result[len] = '\0';

    lua_pushstring(L, result);
    free(result);
    return 1;
}

static const struct luaL_Reg strlib[] = {
    {"upper", str_upper},
    {"reverse", str_reverse},
    {NULL, NULL}
};

int luaopen_strlib(lua_State *L) {
    luaL_newlib(L, strlib);
    return 1;
}

Makefile示例

# Makefile
CC = gcc
CFLAGS = -Wall -fPIC -I/usr/include/lua5.3
LDFLAGS = -shared -llua5.3

all: mylib.so strlib.so point.so

mylib.so: mylib.c
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<

strlib.so: strlib.c
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<

point.so: point.c
	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lm

clean:
	rm -f *.so

install:
	cp *.so /usr/local/lib/lua/5.3/

.PHONY: all clean install
重要提示:
练习题:
  1. 创建一个C扩展,实现文件MD5计算
  2. 实现一个Vector类,支持向量运算
  3. 编写JSON解析的C扩展
  4. 创建一个HTTP客户端C库