MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C 语言与Lua脚本调用

2024-03-063.4k 阅读

C 语言与 Lua 脚本交互基础

Lua 简介

Lua 是一种轻量级、高效且可嵌入的脚本语言。它设计初衷就是为了能够轻松地被集成到其他应用程序中,为其提供灵活的脚本功能。Lua 具有简洁的语法,类似于 Pascal 和 C 语言的风格,这使得它容易被学习和使用。Lua 的核心非常小巧,这使得它在内存受限的环境中也能很好地运行,例如嵌入式系统。同时,Lua 拥有强大的扩展机制,通过模块和库的方式可以实现丰富的功能。

C 语言调用 Lua 脚本的优势

  1. 灵活性:C 语言通常用于开发性能要求高的底层应用,但它缺乏脚本语言所具备的灵活性。通过调用 Lua 脚本,C 应用程序可以在运行时动态加载和执行脚本代码,实现功能的动态扩展。例如,在游戏开发中,可以使用 Lua 脚本来定义游戏关卡、角色行为等,而无需重新编译 C 语言代码。
  2. 快速开发:Lua 语言简洁高效,开发速度快。对于一些非核心业务逻辑,使用 Lua 脚本来实现可以大大缩短开发周期。同时,Lua 脚本可以在不影响 C 语言核心代码稳定性的前提下进行修改和调试,降低了开发成本。
  3. 跨平台性:Lua 脚本可以在不同的操作系统和硬件平台上运行,只要该平台有相应的 Lua 解释器。这使得基于 C 语言和 Lua 脚本结合开发的应用程序具有更好的跨平台性,减少了针对不同平台的重复开发工作。

Lua 环境的创建与初始化

在 C 语言中引入 Lua 库

要在 C 语言程序中调用 Lua 脚本,首先需要在项目中引入 Lua 库。通常,Lua 库以头文件和静态库(或动态库)的形式提供。在 Unix - like 系统中,可以通过包管理器安装 Lua 开发包,例如在 Ubuntu 上可以使用 sudo apt - get install lua5.3 - dev 命令安装 Lua 5.3 的开发包。安装完成后,在 C 代码中通过 #include <lua.h>#include <lualib.h>#include <lauxlib.h> 引入必要的头文件。

创建 Lua 状态机

在 C 语言中,Lua 的操作都是围绕一个 Lua 状态机(lua_State)进行的。通过 luaL_newstate() 函数可以创建一个新的 Lua 状态机。以下是一个简单的示例代码:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        // 处理创建失败的情况
        return 1;
    }
    // 后续操作
    lua_close(L);
    return 0;
}

在上述代码中,luaL_newstate() 函数创建了一个新的 Lua 状态机,并返回一个指向该状态机的指针 L。如果创建失败,函数将返回 NULL。在程序结束时,通过 lua_close(L) 关闭 Lua 状态机,释放相关资源。

初始化 Lua 标准库

创建好 Lua 状态机后,通常需要初始化 Lua 的标准库。Lua 提供了 luaL_openlibs() 函数来完成这个操作。这个函数会打开 Lua 的基本库、数学库、字符串库等一系列标准库。修改上述代码如下:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    // 后续操作
    lua_close(L);
    return 0;
}

通过调用 luaL_openlibs(L),Lua 状态机就具备了标准库提供的各种功能,例如数学计算、字符串处理等。这些功能在后续调用 Lua 脚本时会非常有用。

加载与执行 Lua 脚本

加载 Lua 脚本文件

在 C 语言中,可以使用 luaL_dofile() 函数来加载并执行一个 Lua 脚本文件。该函数接受 Lua 状态机指针和脚本文件名作为参数。以下是一个示例,假设存在一个名为 test.lua 的脚本文件:

-- test.lua
print("Hello from Lua!")
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    if (luaL_dofile(L, "test.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}

在上述 C 代码中,luaL_dofile(L, "test.lua") 尝试加载并执行 test.lua 脚本。如果加载或执行过程中发生错误,函数将返回一个非 LUA_OK 的错误码。通过 lua_tostring(L, -1) 获取错误信息并打印,然后使用 lua_pop(L, 1) 弹出栈顶的错误信息。

加载并执行 Lua 脚本字符串

除了加载脚本文件,还可以直接在 C 语言中加载并执行 Lua 脚本字符串。使用 luaL_dostring() 函数可以实现这一功能。以下是示例代码:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    const char *luaCode = "print('Hello from Lua string!')";
    if (luaL_dostring(L, luaCode) != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua string: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}

在这个例子中,定义了一个 Lua 脚本字符串 luaCode,然后通过 luaL_dostring(L, luaCode) 加载并执行该字符串中的 Lua 代码。同样,通过检查返回值来处理可能出现的错误。

C 语言与 Lua 之间的数据传递

从 C 语言传递数据到 Lua

  1. 传递基本数据类型
    • 整数:可以使用 lua_pushinteger() 函数将一个整数压入 Lua 栈,然后在 Lua 脚本中通过适当的方式获取。例如:
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    int num = 42;
    lua_pushinteger(L, num);
    lua_setglobal(L, "myInt");
    if (luaL_dofile(L, "test2.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}
-- test2.lua
print("The integer from C is:", myInt)

在 C 代码中,lua_pushinteger(L, num) 将整数 num 压入 Lua 栈,然后 lua_setglobal(L, "myInt") 将栈顶元素设置为 Lua 全局变量 myInt。在 Lua 脚本中就可以直接使用这个变量。

  • 浮点数:使用 lua_pushnumber() 函数传递浮点数。例如:
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    double dnum = 3.14;
    lua_pushnumber(L, dnum);
    lua_setglobal(L, "myDouble");
    if (luaL_dofile(L, "test3.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}
-- test3.lua
print("The double from C is:", myDouble)
  1. 传递字符串:通过 lua_pushstring() 函数传递字符串。示例如下:
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    const char *str = "Hello from C";
    lua_pushstring(L, str);
    lua_setglobal(L, "myString");
    if (luaL_dofile(L, "test4.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}
-- test4.lua
print("The string from C is:", myString)
  1. 传递表(类似数组或字典):在 Lua 中,表是一种非常重要的数据结构。要在 C 语言中创建并传递一个表到 Lua,可以按照以下步骤进行。首先使用 lua_newtable() 创建一个新的表,然后通过 lua_push* 系列函数将键值对压入栈,并使用 lua_settable() 函数将键值对设置到表中。示例代码如下:
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    lua_newtable(L);
    // 设置表的键值对
    lua_pushstring(L, "name");
    lua_pushstring(L, "John");
    lua_settable(L, -3);
    lua_pushstring(L, "age");
    lua_pushinteger(L, 30);
    lua_settable(L, -3);
    lua_setglobal(L, "myTable");
    if (luaL_dofile(L, "test5.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}
-- test5.lua
print("Name from table:", myTable.name)
print("Age from table:", myTable.age)

在上述 C 代码中,lua_newtable(L) 创建了一个新的表,lua_pushstring(L, "name")lua_pushstring(L, "John") 分别将键和值压入栈,然后 lua_settable(L, -3) 将这个键值对设置到表中。同理设置了 age 键值对,最后将表设置为全局变量 myTable 在 Lua 脚本中使用。

从 Lua 传递数据到 C 语言

  1. 获取基本数据类型
    • 整数:假设 Lua 脚本中设置了一个全局整数变量,在 C 语言中可以通过 lua_getglobal() 获取该变量并使用 lua_tointeger() 获取其值。示例如下:
-- test6.lua
globalInt = 100
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    if (luaL_dofile(L, "test6.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_getglobal(L, "globalInt");
    if (!lua_isnumber(L, -1)) {
        printf("Expected a number\n");
    } else {
        int num = lua_tointeger(L, -1);
        printf("The integer from Lua is: %d\n", num);
    }
    lua_pop(L, 1);
    lua_close(L);
    return 0;
}

在上述代码中,先通过 luaL_dofile() 执行 Lua 脚本设置 globalInt 变量,然后 lua_getglobal(L, "globalInt") 将该变量压入栈,通过 lua_tointeger() 获取其整数值并打印。

  • 浮点数:使用 lua_tonumber() 获取 Lua 中的浮点数。示例:
-- test7.lua
globalDouble = 2.718
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    if (luaL_dofile(L, "test7.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_getglobal(L, "globalDouble");
    if (!lua_isnumber(L, -1)) {
        printf("Expected a number\n");
    } else {
        double dnum = lua_tonumber(L, -1);
        printf("The double from Lua is: %f\n", dnum);
    }
    lua_pop(L, 1);
    lua_close(L);
    return 0;
}
  1. 获取字符串:使用 lua_tostring() 获取 Lua 中的字符串。示例:
-- test8.lua
globalString = "Hello from Lua"
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    if (luaL_dofile(L, "test8.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_getglobal(L, "globalString");
    if (!lua_isstring(L, -1)) {
        printf("Expected a string\n");
    } else {
        const char *str = lua_tostring(L, -1);
        printf("The string from Lua is: %s\n", str);
    }
    lua_pop(L, 1);
    lua_close(L);
    return 0;
}
  1. 获取表:要获取 Lua 中的表,首先通过 lua_getglobal() 将表压入栈,然后可以通过 lua_pushinteger()lua_pushstring() 设置键,并使用 lua_gettable() 获取对应的值。示例如下:
-- test9.lua
myTable = {name = "Alice", age = 25}
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    if (luaL_dofile(L, "test9.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_getglobal(L, "myTable");
    if (!lua_istable(L, -1)) {
        printf("Expected a table\n");
    } else {
        // 获取 name 字段
        lua_pushstring(L, "name");
        lua_gettable(L, -2);
        if (!lua_isstring(L, -1)) {
            printf("Expected a string for name\n");
        } else {
            const char *name = lua_tostring(L, -1);
            printf("Name from Lua table: %s\n", name);
        }
        lua_pop(L, 1);
        // 获取 age 字段
        lua_pushstring(L, "age");
        lua_gettable(L, -2);
        if (!lua_isnumber(L, -1)) {
            printf("Expected a number for age\n");
        } else {
            int age = lua_tointeger(L, -1);
            printf("Age from Lua table: %d\n", age);
        }
        lua_pop(L, 1);
    }
    lua_pop(L, 1);
    lua_close(L);
    return 0;
}

在上述代码中,先获取 Lua 中的表 myTable,然后分别获取 nameage 字段的值并打印。

调用 Lua 函数

调用 Lua 全局函数

在 C 语言中调用 Lua 全局函数需要按照一定的步骤。首先,通过 lua_getglobal() 将函数压入栈,然后将函数的参数按顺序压入栈,接着使用 lua_pcall() 调用函数,最后从栈中获取函数的返回值。以下是一个简单的示例,假设 Lua 脚本中有一个全局函数 add 用于计算两个数的和:

-- test10.lua
function add(a, b)
    return a + b
end
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    if (luaL_dofile(L, "test10.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_getglobal(L, "add");
    lua_pushinteger(L, 3);
    lua_pushinteger(L, 5);
    if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error calling Lua function: %s\n", error);
        lua_pop(L, 1);
    }
    if (!lua_isnumber(L, -1)) {
        printf("Expected a number as return value\n");
    } else {
        int result = lua_tointeger(L, -1);
        printf("The result of add is: %d\n", result);
    }
    lua_pop(L, 1);
    lua_close(L);
    return 0;
}

在上述 C 代码中,lua_getglobal(L, "add")add 函数压入栈,lua_pushinteger(L, 3)lua_pushinteger(L, 5) 将函数参数压入栈。lua_pcall(L, 2, 1, 0) 调用函数,其中 2 表示参数个数,1 表示期望的返回值个数,0 表示错误处理函数在栈中的位置(这里为 0 表示不使用错误处理函数)。调用成功后,从栈顶获取返回值并打印。

调用 Lua 匿名函数

除了调用全局函数,也可以在 C 语言中调用 Lua 的匿名函数。首先将匿名函数的代码作为字符串加载到 Lua 环境中,然后通过 lua_loadstring()luaL_dostring() 等函数将其编译成可调用的函数对象并压入栈,后续调用方式与调用全局函数类似。示例如下:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    const char *funcStr = "function() return 4 * 5 end";
    if (luaL_loadstring(L, funcStr) != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua function string: %s\n", error);
        lua_pop(L, 1);
    }
    if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error calling Lua function: %s\n", error);
        lua_pop(L, 1);
    }
    if (!lua_isnumber(L, -1)) {
        printf("Expected a number as return value\n");
    } else {
        int result = lua_tointeger(L, -1);
        printf("The result of the anonymous function is: %d\n", result);
    }
    lua_pop(L, 1);
    lua_close(L);
    return 0;
}

在这个例子中,定义了一个匿名函数字符串 funcStr,通过 luaL_loadstring(L, funcStr) 将其编译成函数对象压入栈,然后使用 lua_pcall() 调用该函数并获取返回值。

C 语言中注册 Lua 函数

定义 C 语言函数作为 Lua 可调用函数

在 C 语言中,可以将 C 函数注册到 Lua 环境中,使得 Lua 脚本能够调用这些 C 函数。首先需要定义一个符合 Lua 调用规范的 C 函数。C 函数的参数和返回值通过 Lua 栈来传递。函数的原型为 int (*lua_CFunction) (lua_State *L),其中 L 是 Lua 状态机指针。以下是一个简单的 C 函数示例,用于计算两个整数的乘积:

static int multiply(lua_State *L) {
    int a = lua_tointeger(L, 1);
    int b = lua_tointeger(L, 2);
    int result = a * b;
    lua_pushinteger(L, result);
    return 1;
}

在上述函数中,lua_tointeger(L, 1)lua_tointeger(L, 2) 从 Lua 栈中获取前两个参数,计算乘积后,lua_pushinteger(L, result) 将结果压入栈,最后 return 1 表示返回一个值。

注册 C 函数到 Lua 环境

要将上述 C 函数注册到 Lua 环境中,可以使用 lua_register() 函数。示例如下:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

static int multiply(lua_State *L) {
    int a = lua_tointeger(L, 1);
    int b = lua_tointeger(L, 2);
    int result = a * b;
    lua_pushinteger(L, result);
    return 1;
}

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    lua_register(L, "multiply", multiply);
    if (luaL_dofile(L, "test11.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}
-- test11.lua
result = multiply(3, 4)
print("The result of multiply is:", result)

在 C 代码中,lua_register(L, "multiply", multiply)multiply 这个 C 函数注册到 Lua 环境中,名称为 multiply。这样在 Lua 脚本 test11.lua 中就可以直接调用这个函数。

使用 luaL_Reg 结构体注册多个函数

当需要注册多个 C 函数到 Lua 环境时,可以使用 luaL_Reg 结构体。luaL_Reg 结构体定义如下:

typedef struct luaL_Reg {
    const char *name;
    lua_CFunction func;
} luaL_Reg;

以下是一个示例,注册多个函数到 Lua 环境:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

static int add(lua_State *L) {
    int a = lua_tointeger(L, 1);
    int b = lua_tointeger(L, 2);
    int result = a + b;
    lua_pushinteger(L, result);
    return 1;
}

static int subtract(lua_State *L) {
    int a = lua_tointeger(L, 1);
    int b = lua_tointeger(L, 2);
    int result = a - b;
    lua_pushinteger(L, result);
    return 1;
}

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    luaL_Reg funcs[] = {
        {"add", add},
        {"subtract", subtract},
        {NULL, NULL}
    };
    luaL_register(L, NULL, funcs);
    if (luaL_dofile(L, "test12.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}
-- test12.lua
addResult = add(5, 3)
subtractResult = subtract(5, 3)
print("Add result:", addResult)
print("Subtract result:", subtractResult)

在上述代码中,定义了 addsubtract 两个 C 函数,并通过 luaL_Reg 结构体数组 funcs 进行组织。luaL_register(L, NULL, funcs) 将这些函数注册到 Lua 环境中,Lua 脚本 test12.lua 可以调用这些注册的函数。

错误处理

Lua 脚本执行错误处理

在加载和执行 Lua 脚本时,可能会出现各种错误,如语法错误、运行时错误等。通过 luaL_dofile()luaL_dostring() 等函数的返回值可以判断是否发生错误。如果返回值不是 LUA_OK,可以通过 lua_tostring(L, -1) 获取错误信息。例如:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    if (luaL_dofile(L, "errorTest.lua") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error loading Lua script: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}

假设 errorTest.lua 中存在语法错误,例如:

-- errorTest.lua
print("This is a wrong script syntax error: missing end")

在 C 代码中,luaL_dofile() 会返回错误码,通过 lua_tostring(L, -1) 获取错误信息并打印。

C 与 Lua 交互函数调用错误处理

在调用 Lua 函数(如 lua_pcall())或注册 C 函数到 Lua 环境(如 lua_register())时也可能发生错误。lua_pcall() 的返回值可以判断函数调用是否成功,如果失败可以通过 lua_tostring(L, -1) 获取错误信息。对于 lua_register(),虽然它本身没有返回值表示错误,但如果在注册函数过程中出现问题(例如函数名冲突等),可能会导致后续在 Lua 中调用该函数失败,同样可以通过 lua_pcall() 的错误处理机制来捕获相关错误。例如:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

static int badFunction(lua_State *L) {
    // 假设这个函数有逻辑错误,这里简单返回错误信息
    lua_pushstring(L, "Error in badFunction");
    return lua_error(L);
}

int main() {
    lua_State *L = luaL_newstate();
    if (L == NULL) {
        return 1;
    }
    luaL_openlibs(L);
    lua_register(L, "badFunction", badFunction);
    if (luaL_dostring(L, "result = badFunction()") != LUA_OK) {
        const char *error = lua_tostring(L, -1);
        printf("Error calling badFunction: %s\n", error);
        lua_pop(L, 1);
    }
    lua_close(L);
    return 0;
}

在上述代码中,badFunction 故意返回错误,通过 luaL_dostring() 调用该函数时捕获错误并打印错误信息。

通过以上全面深入的介绍,涵盖了 C 语言与 Lua 脚本调用的各个方面,包括 Lua 环境创建、脚本加载执行、数据传递、函数调用、注册以及错误处理等,希望能帮助开发者更好地在项目中实现 C 语言与 Lua 脚本的高效交互。