文章目录

  • 前言
  • Lua与C的基本交互
  • C 调用 Lua
  • Lua 调用 C
  • Lua与C之间的数据类型转换

前言

Lua与C交互是一种非常常见的技术,它可以让我们在C语言中调用Lua脚本,也可以让我们在Lua脚本中调用C语言函数。这种交互方式可以使得我们在不同的编程语言之间实现混合编程,从而达到更高的灵活性和效率。

Lua栈是Lua与C交互的核心数据结构,所有的数据交互都是通过Lua栈来完成的。无论是从Lua中调用C函数,还是从C中调用Lua函数,都需要使用Lua栈来传递参数和返回值。

Lua与C的基本交互

Lua与C的基本交互是通过Lua提供的C API来实现的。C API是一组C语言函数,它们可以让我们在C语言中直接操作Lua对象,包括创建Lua对象、读取和修改Lua对象的值、执行Lua代码等。

  1. lua.h

lua.h 是 Lua 解释器的核心头文件,包含了 Lua 解释器的基本数据类型和函数原型。在使用 Lua 解释器时,必须包含该头文件。

常用的数据类型包括:

  • lua_State:Lua 解释器状态,用于保存 Lua 程序的运行状态和数据栈。
  • lua_Number:Lua 的数字类型,可以是整数或者浮点数。
  • lua_Integer:Lua 的整数类型,通常用于表示数组下标或者计数器。
  • lua_CFunction:C 函数指针类型,用于定义 Lua 扩展函数。
  • lua_XXX:其他常用数据类型,如字符串、表、函数等。

常用的函数原型包括:

  • lua_newstate:创建一个 Lua 解释器状态。
  • lua_close:关闭一个 Lua 解释器状态。
  • lua_load:将 Lua 代码加载到解释器中。
  • lua_pcall:调用 Lua 函数并处理异常。
  • lua_pushXXX:将数据压入 Lua 数据栈中。
  • lua_toXXX:从 Lua 数据栈中取出数据。
  1. lualib.h

lualib.h 是 Lua 标准库的头文件,包含了一些常用的函数和库的函数原型。在使用 Lua 标准库时,必须包含该头文件。

常用的库包括:

  • lua_math:数学库,提供了常用的数学函数。
  • lua_string:字符串库,提供了字符串操作函数。
  • lua_table:表库,提供了表操作函数。
  • lua_io:I/O 库,提供了文件和网络操作函数。
  • lua_os:操作系统库,提供了系统调用函数。

常用的函数原型包括:

  • luaL_newlib:创建一个 Lua 标准库扩展。
  • luaL_loadfile:加载一个 Lua 脚本文件。
  • luaL_dofile:加载并执行一个 Lua 脚本文件。
  • luaL_checkXXX:从 Lua 数据栈中取出数据并检查类型。
  • luaL_optXXX:从 Lua 数据栈中取出可选参数并设置默认值。
  • luaL_error:抛出一个 Lua 异常。
  1. lauxlib.h

lauxlib.h 是 Lua 辅助库的头文件,包含了一些辅助函数和库的函数原型。在编写 Lua 扩展模块或者使用 Lua 辅助库时,必须包含该头文件。

常用的函数包括:

  • luaL_checkversion:检查 Lua 版本是否兼容。
  • luaL_checkstack:检查 Lua 数据栈是否有足够的空间。
  • luaL_newmetatable:创建一个新的元表。
  • luaL_setmetatable:将元表设置到一个 Lua 对象上。
  • luaL_ref:将 Lua 对象的引用压入数据栈并返回引用索引。
  • luaL_unref:释放一个 Lua 对象的引用。
  • luaL_loadbuffer:将 Lua 代码加载到解释器中。
  • luaL_typename:获取 Lua 对象的类型名称。
  • luaL_argerror:抛出一个参数错误异常。

C 调用 Lua

简单示例:

makefile

这里用的是luajit,如果是lua就等价换为 -llua。

CC=gcc
CFLAGS=-Wall -g
LDFLAGS=-lluajit-5.1 -lm
LDFLAGS += -L/usr/local/lib

.PHONY: all clean

all: main

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

clean:
        rm -f *.o main

main.c

#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <luajit.h> // luajit才需要

void dump(lua_State *L) {
    int n = lua_gettop(L);
    for (int i = 1; i <= n; i++) {
        int type = lua_type(L, i);
        const char* str = lua_tostring(L, i);
        printf("%d: %s(%s)\n", i, str, lua_typename(L, type));
    }
}
int main() {
    lua_State *L = luaL_newstate();// 创建Lua状态机
    luaL_openlibs(L);// 打开Lua标准库

    if (luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0)) {// 加载并执行test.lua文件
        printf("Error: %s\n", lua_tostring(L, -1));// 如果出错,打印错误信息
        return 1;
    }

    lua_getglobal(L, "add");// 获取全局变量add
    lua_pushnumber(L, 10);// 压入第一个参数
    lua_pushnumber(L, 5);// 压入第二个参数
    lua_pcall(L, 2, 1, 0);// 调用add函数并传递两个参数,期望返回1个结果

    if (lua_isnumber(L, -1)) {// 如果返回值是一个数字
        double res = lua_tonumber(L, -1); // 获取返回值
        printf("res = %f\n", res); // 打印返回值
    }
    
    lua_getglobal(L, "sub");
    lua_pushnumber(L, 10);
    lua_pushnumber(L, 5);
    lua_pcall(L, 2, 1, 0);

    if (lua_isnumber(L, -1)) {
        double res = lua_tonumber(L, -1);
        printf("res = %f\n", res);
    }

    dump(L); 
    /* lua_tonumber() 不会弹出元素
    1: 15(number)
	2: 5(number)
	*/
    lua_close(L);// 销毁Lua状态机
    return 0;
}

test.lua

function add(a, b)
    return a + b
end
function sub(a, b)
    return a - b
end

更复杂的案例:

  • 忽略LuaBridge,用于C++和Lua交互的一个C++库。
  • 注释详细写了当前栈中的状态。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cassert>
extern "C" {
#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>
}
#include <LuaBridge/LuaBridge.h>

using namespace std;
using namespace luabridge;


/*
 * _G = {
 *      printMsg = function 
 *      my_pow = function
 * }
 */
const char* script1 = R"(
function printMsg()
    print("Hello World")
end
printMsg()
function my_pow(x, y) -- 要拿到函数function 好像local my_pow = function 不可:update(2023年5月11日):全局函数!
    return x ^ y
end
)";

/*
 * _G = {
 *      "pkg" = {
 *          {__index = _G}
 *          printMsg 
 *          my_pow
 *      }
 * }
 */
const char* script2 = R"(
    print("script2")
    pkg.printMsg()
)";

void call_errno(lua_State *L) {
    if (lua_isstring(L, -1)) { // 1 true; 0 false
        auto msg = lua_tostring(L, -1); 
        printf("load script1 failed : %s\n", msg);
        lua_pop(L, 1);
    }
}

int main() {
    lua_State *L = luaL_newstate(); 
    luaL_openlibs(L);
    
    // -> lua_load (load chunk to stack && exec chunk) (chunk 的第一个upvalue是_ENV )
    auto res = luaL_loadbuffer(L, script1, strlen(script1), "script1");  
    if (res != LUA_OK) { // load script1 failed -> return msg to the top of stack 
        call_errno(L); 
        return -1;
    }
    
    // call the chunk
    if (lua_pcall(L, 0, 0, 0) != LUA_OK) { // output : Hello World
        call_errno(L);
        return -1;
    }

    // call my_pow()
    res = lua_getglobal(L, "my_pow");
    const char *a = lua_tostring(L, -1);
    if (!lua_isfunction(L, -1)) {
        call_errno(L);
        return -1;
    }
    
    // [stack] -> chunk ; my_pow;
    lua_pushnumber(L, 2); 
    lua_pushnumber(L, 3); // [stack] -> chunk; my_pow; 2; 3;

    if (lua_pcall(L, 2, 1, 0) != LUA_OK) { // [stack] -> chunk; my_pow; 2^3;
        call_errno(L);
        return -1;
    } // [stack] -> chunk; 8.0;
    if (!lua_isnumber(L, -1)) {
        call_errno(L); 
        return -1;
    }
    res = lua_tonumber(L, -1);
    lua_pop(L, 1); // pop 8  
    
    printf("call function (my_pow) result : %d\n", res);

    // clear stack
    lua_settop(L, 0); // num = lua_gettop(L); lua_pop(L, num);

    /* -----------------------以上是对加载script1的简单使用-------------------------------------------- */
    
    // local script1 && package 
    // 以下不做过多的判误操作了,默认写的全对( ^_^ )
    luaL_loadbuffer(L, script1, strlen(script1), "script1");  

    lua_getglobal(L, "_G");     // [stack] -> chunk; _G; 
    lua_newtable(L);            // [stack] -> chunk; _G; newtable;
    lua_pushstring(L, "pkg");   // [stack] -> chunk; _G; newtable; "pkg";
    lua_pushvalue(L, -2);       // [stack] -> chunk; _G; newtable; "pkg"; newtable;
    // rawset -> (t[k] = v); -2: key; -1: val; t: second param; 
    lua_rawset(L, -4);          // [stack] -> chunk; _G; newtable; 
    
    // setupvalue -> (param: funcindex, n) (index处的函数设置第n个upvalue)
    const char * upvalueName = lua_setupvalue(L, -3, 1);                    // [stack] -> chunk; _G;
    assert(strcmp(upvalueName, "_ENV") == 0);
    lua_newtable(L);                // [stack] -> chunk; _G; metatable;
    lua_pushstring(L, "__index");   // [stack] -> chunk; _G; metatable; "__index";
    lua_pushvalue(L, -3);           // [stack] -> chunk; _G; metatable; "__index"; _G;
    lua_rawset(L, -3);              // [stack] -> chunk; _G; metatable; 
    lua_pushstring(L, "pkg");       // [stack] -> chunk; _G; metatable; "pkg";
    // rawget -> (t[k]) -1: key; t: second param;
    lua_rawget(L, -3);              // [stack] -> chunk; _G: metatable; pkg(table);
    lua_pushvalue(L, -2);           // [stack] -> chunk; _G: metatable; pkg(table); metatable;
    lua_setmetatable(L, -2);        // [stack] -> chunk; _G: metatable; pkg(table); 
    lua_pop(L, 3);                  // [stack] -> chunk;

    luaL_loadbuffer(L, script2, strlen(script2), "script2");  
    lua_pcall(L, 0, 0, 0);

    lua_close(L);
    return 0;
}

Lua 调用 C

简单示例:

main.c

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

int add(lua_State *L) {// 定义一个C函数
    double a = luaL_checknumber(L, 1);// 从Lua栈中获取第一个参数
    double b = luaL_checknumber(L, 2);// 从Lua栈中获取第二个参数
    double res = a + b;
    lua_pushnumber(L, res);// 将结果压入Lua栈中
    return 1;// 返回值的数量
}

int main() {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    lua_pushcfunction(L, add);// 注册add函数
    lua_setglobal(L, "add");// 将add函数设置为全局变量

    if (luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0)) {// 加载并执行test.lua文件
        printf("Error: %s\n", lua_tostring(L, -1));
        return 1;
    }

    lua_close(L);
    return 0;
}

test.lua

local res = add(10, 5)
print(res)

更复杂的案例:

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
extern "C" {
#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>
}
#include <LuaBridge/LuaBridge.h>

using namespace std;
using namespace luabridge;

const char *script = R"(
    local val = pow_from_c(2, 3);
    print(val)
)";

/**
 *  _G = {
 *      "pow_from_c" = function
 *  }
 */

int pow_from_c(lua_State *L) {
    int param_cnt = lua_gettop(L);
    if (param_cnt != 2) return 0;
    if ( lua_isinteger(L, 2) && lua_isinteger(L, 1) ) {
        auto x = lua_tonumber(L, 1);
        auto y = lua_tonumber(L, 2);
        int res = (int)pow(x, y);
        lua_pushnumber(L, res);
        return 1;
    }
    return 0;
}

int main() {
    lua_State *L = luaL_newstate(); 
    luaL_openlibs(L);

    lua_getglobal(L, "_G");
    lua_pushstring(L, "pow_from_c");
    lua_pushcclosure(L, pow_from_c, 0); // [stack] -> _G; "pow_from_c"; closure;
    lua_rawset(L, -3);
    lua_pop(L, 1); 

    luaL_loadbuffer(L, script, strlen(script), "script");
    lua_pcall(L, 0, 0, 0);

    lua_close(L);
    return 0;
}

大部分情况下,我们还是用模块的方式来提供一组函数,我们在Lua中可以加载这个模块。

注释中有拓展一些知识点

main.cpp

#include <iostream>
#include <algorithm>

#ifdef __cplusplus
extern "C" {
#endif

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

extern "C" int luaopen_mylib(lua_State *L);

#ifdef __cplusplus
}
#endif
using namespace std;

// a + b
static int l_add(lua_State* L) {
    double a = luaL_checknumber(L, 1); // luaL_check*: 检查符合指定类型转换并返回;不是则抛出错误,中断函数执行。
    // luaL_to*: 转换失败返回默认值
    double b = luaL_checknumber(L, 2);
    lua_pushnumber(L, a + b);
    return 1;
}

// a / b
static int l_div(lua_State* L) {
    double a = luaL_checknumber(L, 1);
    double b = luaL_checknumber(L, 2);
    if (b == 0) {
        luaL_error(L, "division by zero");
    }
    lua_pushnumber(L, a / b);
    return 1;
}

// ab
static int l_concat(lua_State* L) {
    const char* a = luaL_checkstring(L, 1);
    const char* b = luaL_checkstring(L, 2);
    lua_pushstring(L, a);
    lua_pushstring(L, b);
    lua_concat(L, 2);
    return 1;
}

// userdata: 一块内存区域,可以在C代码中分配和管理,然后传递给Lua脚本使用
// lua_touserdata 获取对应指针
// 被gc检测没被引用时,会调用C代码中定义的__gc元方法回收。
typedef struct {
    int x, y;
} Point;

static int l_newpoint(lua_State* L) {
    Point *p = (Point *)lua_newuserdata(L, sizeof(Point));
    p->x = luaL_checknumber(L, 1);
    p->y = luaL_checknumber(L, 2);
    return 1;
}

static int l_getpoint(lua_State* L) {
    Point *p = (Point *)lua_touserdata(L, 1);
    cout << "[ x = " << p->x << ", y = " << p->y << " ]" << endl;
    return 1;
}

// light userdata: 指针,通常用于将一些C语言中的数据结构传给Lua脚本
// lua_topointer 获取对应指针
// 需要显示释放这块内存,没被引用时不会调用任何元方法。
static int l_newlight(lua_State* L) {
    Point* p = (Point *)malloc(sizeof(Point));
    p->x = luaL_checknumber(L, 1);
    p->y = luaL_checknumber(L, 2);
    lua_pushlightuserdata(L, p); // 返回一个指针,由C自己管理
    return 1;
}

static int l_getlight(lua_State* L) {
    Point *p = (Point *)lua_topointer(L, 1);
    cout << "[ x = " << p->x << ", y = " << p->y << " ]" << endl;
    return 0;
}

// 注册名为 `mylib` 的Lua模块,最后一项用于数组标记结尾
// luaL_Reg结构体有两个成员:函数名和函数指针
static const struct luaL_Reg mylib[] = {
    {"add", l_add},
    {"div", l_div},
    {"concat", l_concat},
    {"newpoint", l_newpoint},
    {"getpoint", l_getpoint},
    {"newlight", l_newlight},
    {"getlight", l_getlight},
    {NULL, NULL}
};

// Lua C API 中,所有用于打开和关闭模块的函数都应该以:
// luaopen_ 前缀开头。
int luaopen_mylib(lua_State *L) {
    // luaL_newlib(L, mylib); // 适用于创建新的模块表并注册函数
    luaL_register(L, "mylib", mylib); // 适用于向已有的表中注册函数 (最新版本的Lua:已弃用)
    return 1;
} // 将一组C函数注册到表中,然后将这个表返回给Lua 解释器

test.lua

local mylib = require "mylib"
local add = mylib.add
local div = mylib.div
local concat = mylib.concat
local newpoint = mylib.newpoint
local getpoint = mylib.getpoint
local newlight = mylib.newlight
local getlight = mylib.getlight

--[[
--   在lua中,每个函数调用都会创建一个新的栈帧,栈帧是一个独立的空间,用于存储改函数执行时需要的数据。
--]]

print(add(1, 2))
print(div(1, 2))

local s1 = "Hello"
local s2 = "World"
print(concat(s1, s2))

local x, y = 3, 4
local p = newpoint(x, y)
getpoint(p)


local p1 = newlight(x + 1, y - 1)
getlight(p1)

该段代码执行的makefile文件:

CC=g++
CFLAGS=-Wall -g
LDFLAGS=-shared -fPIC -lluajit-5.1 -lm
LDFLAGS += -L/usr/local/lib

TARGET=mylib.so
SRC=main.cpp

all: $(TARGET)

$(TARGET): $(SRC)
        $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)

clean:
        rm -f $(TARGET)

README:(只是用于记录)

[ 编译.o 文件 ]
- g++ *.cpp -c -I../include -L../lib -llua
----
- -llua : [ 我在 ../lib 中用 *.o 生成了一个 lua.a ]
- liblua.a 在lib、include都有,不用自己生成

[ 生成静态库 .a ]
- ar rs *.a *.o

[ 生成动态库 .so ]
- g++ *.cpp -I../include -fPIC -shared -o *.so

Lua与C之间的数据类型转换

  1. 数字类型转换:Lua中的数字类型为lua_Number,它可以是double或long long等类型。在C语言中,我们可以使用lua_Number类型来表示Lua中的数字类型。
  2. 字符串类型转换:Lua中的字符串类型为const char*,在C语言中也是使用char*类型表示。在Lua中,字符串可以包含任意的二进制数据,因此在C语言中读取Lua字符串时需要进行一些特殊处理。
  3. 表类型转换:Lua中的表类型可以使用lua_newtable函数创建,然后使用lua_settable或lua_rawset函数向表中添加元素。在C语言中,我们可以使用lua_gettable或lua_rawget函数获取表中的元素。
  4. 函数类型转换:Lua中的函数类型可以通过lua_pushcfunction函数将一个C函数压入Lua栈中。在C语言中,我们可以通过lua_isfunction和lua_tocfunction函数判断和获取Lua栈中的函数。

Lua中的数据类型可以通过lua_State结构体来访问,而C中的数据类型可以通过特定的函数来进行访问。

Lua中的数据类型可以通过压栈和弹栈的方式来进行传递,而C中的数据类型则需要通过参数传递或返回值来进行传递。

常见的数据类型转换方法:

  1. 将Lua中的整数转换为C中的整数
int lua_tointeger(lua_State *L, int index);  //将指定位置的栈值转换为整数
long lua_tointegerx(lua_State *L, int index, int *isnum);  //将指定位置的栈值转换为长整数
  1. 将Lua中的浮点数转换为C中的浮点数
double lua_tonumber(lua_State *L, int index);  //将指定位置的栈值转换为浮点数
double lua_tonumberx(lua_State *L, int index, int *isnum);  //将指定位置的栈值转换为浮点数
  1. 将Lua中的字符串转换为C中的字符串
const char *lua_tostring(lua_State *L, int index);  //将指定位置的栈值转换为字符串
size_t lua_strlen(lua_State *L, int index);  //获取指定位置的栈值的长度
  1. 将C中的整数转换为Lua中的整数
void lua_pushinteger(lua_State *L, lua_Integer n);  //将整数压入栈中
  1. 将C中的浮点数转换为Lua中的浮点数
void lua_pushnumber(lua_State *L, lua_Number n);  //将浮点数压入栈中
  1. 将C中的字符串转换为Lua中的字符串
void lua_pushstring(lua_State *L, const char *s);  //将字符串压入栈中
  1. 将C中的指针转换为Lua中的light userdata
void lua_pushlightuserdata(lua_State *L, void *p);  //将指针压入栈中
  1. 将Lua中的table转换为C中的数组或结构体
int lua_gettable(lua_State *L, int index);  //从栈中获取指定位置的table,并将其压入栈中
int lua_getfield(lua_State *L, int index, const char *k);  //从栈中获取指定位置的table的指定字段,并将其压入栈中
int lua_rawgeti(lua_State *L, int index, lua_Integer n);  //从栈中获取指定位置的table的指定索引,并将其压入栈中
  1. 将C中的数组或结构体转换为Lua中的table
void lua_newtable(lua_State *L);  //创建一个新的空table,并将其压入栈中
void lua_settable(lua_State *L, int index);  //从栈中获取指定位置的table和key-value对,并将其设置到table中
void lua_setfield(lua_State *L, int index, const char *k);  //从栈中获取指定位置的table和value,并将其设置到table的指定字段中
void lua_rawseti(lua_State *L, int index, lua_Integer n);  //从栈中获取指定位置的table和value,并将其设置到table的指定索引中