vlambda博客
学习文章列表

Lua与C语言的互相调用

Lua底层是C语言开发的,在设计时就考虑到了与C语言的互操作性。可以把Lua作为一种独立的语言或者作为一种嵌入式的脚本语言。有许多游戏和其他应用程序利用Lua作为脚本语言。

在本文中,我们将重点探讨如何将Lua嵌入到C或C++应用程序中,作为胶水脚本存在。虽然此文只关注C语言的API,但在LuaDardo项目中,这些C API并没有做根本改变,只是在命名规范上做了适应Dart语言的变化(驼峰命名法)。简单说,LuaDardo[1]基本是兼容这些API的。

使用 C API

Lua的C语言API是高效和轻量级的。在编写任何C语言代码之前,有几个重要的头文件需要我们熟悉:

  • lua.h:提供使用Lua所需的所有函数。这个文件中的所有函数都以lua_为前缀。

  • luaxlib.h:使用lua.h中公开的公共API,为常见任务提供更高层次的抽象。该文件中的所有函数都以luaL_为前缀。

  • lualib.h:提供标准的Lua库。这个文件中的所有函数也都以luaL_为前缀。

Lua不分配任何全局内存。相反,它将所有的状态存储在一个叫做lua_State的结构中。这个结构包含了Lua运行时所需的一切。在lua_State对象周围放置一个互斥锁是一个快速而简单的方法,可以确保任何Lua实例都是线程安全的。在一个应用程序中创建多个状态并因此创建多个Lua运行时是完全有效的,尽管这样做的用例不多。

要创建一个新的Lua状态,请调用luaL_newstate()函数,它将返回一个指向lua_State结构的指针。这个指针需要传递给所有未来的Lua API调用——这就是 Lua 知道它正在使用什么运行时的方式。在你的程序运行完毕后,用lua_close(lua_State*)函数销毁这个状态。

当你创建一个新的Lua状态时,标准Lua库不会被自动加载。这可能是个问题,因为Lua程序员至少会期望准Lua库是可用的。你可以用luaL_openlibs(lua_State*)函数加载标准库。

下面的代码演示了如何设置和销毁嵌入式 Lua 运行时:

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int main(int argc, char** argv) {
    // 首先, 创建一个新的lua state
    lua_State *L = luaL_newstate();
    // 接下来, 加载所有的标准库
    luaL_openlibs(L);
    
    //在此处编写与 Lua 运行时交互的代码
    
    // 最后,销毁lua state
    lua_close(L);
    return 0;
}

Lua和C是根本不同的语言。它们处理一切的方式都不同,如内存管理、类型,甚至是函数调用。这在试图整合这两种语言时带来了一个问题:我们如何在这两种语言之间进行交流?这就是Lua栈出现的地方。

Lua栈是一个抽象的栈,位于C语言和Lua运行时之间。它是一个后进先出(LIFO)的栈。这个想法是,C和Lua都知道栈的规则,只要它们都遵守这些规则,它们就可以共存和交流。

一般来说,你可以把栈看作是一种共享的数据存储机制。它通常的工作方式是,你在C语言中把一些值推到栈中,然后,你调用一个Lua函数,把控制权交给Lua运行时。运行时将这些值从栈中弹出,相关的函数完成它的工作并将返回值推回栈。然后控制权被交还给C,C将返回值从栈中弹出。

下图展示了这个流程:

push到栈

使用Lua栈的第一步通常是向它推送一些数据。Lua C API提供了几个函数来推送不同类型的数据到栈上。下面的函数可以用来推送数据:

  • lua_pushnil(lua_State*):将nil推入堆栈

  • lua_pushboolean(lua_State*, bool):将一个布尔值压入到栈中

  • lua_pushnumber(lua_State*, lua_Number): 将双精度值压入栈中

  • lua_pushinteger(lua_State*, lua_Integer):将一个有符号的整数压入到栈中

  • lua_pushstring (lua_State*, const char*): 将一个以NULL结尾的字符串压入到栈中

当你将一个字符串压入栈时,Lua创建了它自己的字符串副本。一旦推送操作完成,你就可以修改甚至释放你所拥有的这个字符串的副本。

栈不是无限的。在将数据推入堆栈之前,最好检查一下是否真的有空间容纳这些数据。要检查堆栈有多少空间,你可以使用int lua_checkstack(lua_State*, int) 函数。该函数需要两个参数:Lua状态和你想添加到栈中的项目数量。如果有足够的空间,该函数返回true(1)。如果没有足够的空间,该函数返回false (0)。如果需要的话,这个函数实际上可以增加栈。

查询栈

Lua使用索引来引用栈中的元素。栈底元素索引为1;索引向最后一个元素被添加的栈顶增长。Lua也可以对栈进行反向索引。索引 -1 表示栈顶,-2 表示栈顶正下方的元素,依此类推:

Lua与C语言的互相调用

使用索引,你可以检查栈上任何元素的类型。这里列出的函数可以用来查询栈中元素的类型。它们都返回true(1)或false (0)。每个函数的第一个参数是一个Lua状态,第二个参数是一个栈索引:

  • int lua_isnumber(lua_State*, int):检查提供的索引处的元素是否为数字

  • int lua_isstring(lua_State*, int):检查提供的索引处的元素是否为字符串

  • int lua_isboolean(lua_State*, int):检查所提供索引处的元素是否为布尔值

  • int lua_istable(lua_State*, int):检查给定索引处的元素是否为表

  • int lua_isnil(lua_State*, int):检查给定索引处的元素是否为nil

有一个类似的函数,int lua_type(lua_State*, int),它返回一个带有对象类型的枚举值。当在 switch 语句或类似的东西中使用时,这个函数很有用。以下是该函数可以返回的有效枚举值:

  • LUA_TNUMBER:代表一个Lua数字类型

  • LUA_TSTRING:代表一个Lua字符串类型

  • LUA_TBOOLEAN:代表一个 Lua 布尔值类型

  • LUA_TTABLE:代表一个Lua表类型

  • LUA_TNIL:代表nil类型

从栈中读取

Lua提供了几个函数来检索栈中的值。这里列出了最常见的函数。每个函数的第一个参数是Lua状态,第二个参数是一个整数,即被读取元素的索引。

  • int lua_toboolean(lua_State*, int):返回true (1)或false (0)

  • lua_Number lua_tonumber(lua_State*, int):返回一个双精度值

  • lua_Integer lua_tointeger(lua_State*, int):返回一个整数值

  • const char* lua_tostring(lua_State*, int, size_t*):返回一个指向内部Lua字符串的指针。最后一个参数是可选的;如果它不是NULL,字符串的大小将被写入其中

  • size_t lua_objlen(lua_State*, int):返回与Lua中的#操作符相同的值

当调用lua_tostring时,该函数返回指向内部字符串的指针。返回值是const的,以提醒你不应该修改这个值! 一旦这个值被从栈中弹出,这个字符串就可能不再存在。保留这个函数的返回值是个坏主意——而应该复制并存储它。

栈的大小

你可以通过int lua_gettop(lua_State*)函数来获得Lua栈顶元素的索引。这个函数返回的值将是Lua栈顶元素的索引。

你可以用lua_settop(lua_State*,int)来设置堆栈的顶部元素(大小)。这个函数不返回任何东西。第二个参数是应该成为栈中新的顶部元素的索引的元素。这个函数有效地调整了堆栈的大小。

如果用lua_settop请求的栈大小小于以前的大小,那么所有在新的顶部的元素都会被简单地丢弃。如果要求的栈大小大于先前的大小,所有新的元素将被填充为nil

在lua.h中定义了一个lua_pop(lua_State*, int) 宏,这是一个快捷方式。这个函数将从栈中弹出一些元素,简单地丢弃它们。该函数的第二个参数是要移除多少个元素。

从 C 中读取 Lua 变量

从C语言读写Lua变量非常容易。正因为如此,Lua经常被用作配置语言或保存和加载程序的状态。Lua运行时将为你解析和解释文件,消除了手动解析的需要。而Lua的语法很适合这类任务。在本节中,我们将探讨如何读取作为配置数据的Lua变量。

加载 Lua 文件

要在C语言中读取一个Lua变量,你需要用int luaL_loadfile(lua_State*, const char*)加载Lua文件。然后需要用lua_pcall(lua_State*, int, int, int, int)来执行生成的块。在Lua chunk被加载和执行后,它所声明的任何变量都可以被读取。lua_pcall函数将在本文的 "从C调用Lua函数 "部分详细描述。

luaL_loadfile函数在成功时返回0,或在失败时返回以下枚举之一。lua_errsyntax, lua_errmem, lua_errfile。该函数的第一个参数是要处理的Lua状态,第二个参数是要加载的文件路径。产生的Lua块被添加到Lua栈中。

lua_pcall函数是用来执行Lua块的,这些块通常是函数。这个函数在成功时将返回0,如果失败则返回一个错误代码。此外,如果函数失败,一个错误信息将被推入栈。

lua_pcall的第一个参数是要操作的Lua状态。第二个参数是被调用的块所期望的参数的数量。这个参数只在调用Lua函数时使用,并且应该与函数参数的数量一致。当执行一个从文件(或字符串)加载的chunk时,这个参数应该是0。

lua_pcall的第三个参数是期望chunk返回的结果数量。这里指定的数字是lua_pcall将留在栈中的项目数量。如果这里有一个数字,你要负责从栈中读取和删除这些值。当加载一个简单的文件块时,这个值可以是0。 有些文件(比如模块)会返回一个表或多个值。在这些情况下,这个值不应该是0。lua_pcall的第四个也是最后一个参数是错误处理。值为0的参数告诉Lua在lua_pcall失败时将错误信息推入栈。

读取全局变量

读取一个全局变量分三步进行。你必须使用该变量的名称来把它的值推到栈上。一旦进入栈,该值就可以被C语言读取。在C语言中得到该值后,通过删除旧的副本来清理栈。

你可以使用lua_getglobal (lua_State*, const char *)函数将一个全局变量按名称推入栈。lua_getglobal的第一个参数是Lua状态。第二个参数是将其压入栈的变量名称。此函数不返回任何内容。

lua_getglobal函数使用全局变量的名称,将其值留在栈中。要在C语言中读取这个值,你必须使用lua_to函数之一,比如lua_tonumberlua_tostring。这些函数在本文的 "从栈中读取 "一节中讨论过。一旦你在C语言中得到了变量的值,通过调用lua_pop来清理栈。

例子

让我们看一个如何从Lua中读取变量到C语言的简单例子。考虑一下我们创建一个游戏角色的用例。我们想把该角色的属性存储在一个外部配置文件中,以便于编辑。这个属性文件将看起来像这样:

class = "Warrior"
attack = 56
defense = 43

在C语言中读取配置值将涉及创建一个新的Lua状态,加载Lua文件,将每个变量推入栈,读取并存储这些值,最后将所有的值从栈中弹出:

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

int main(int argc, char** argv) {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    // 加载文件"hero.lua"
    int result = luaL_loadfile(L, "hero.lua");
    if (result != 0) {
        printf("Could not load hero.lua, exiting");
        lua_close(L);
    return -1;
    } 
    
    // 执行已加载的Lua 块
    result = lua_pcall(L, 000);
    if (result != 0) {
        const char* error = lua_tostring(L, -1);
        printf("Error loading hero.lua, exiting.\n");
        printf("Error message %s", error);
        lua_close(L);
        return -1;
    } 
    
 // 获取栈的基本索引
    int stack_base = lua_gettop(L);
    // 将字符属性推入栈
    lua_getglobal(L, "class");   // Index 1
    lua_getglobal(L, "attack");  // Index 2
    lua_getglobal(L, "defense"); // Index 3
    
    // 读取堆栈上每个新入栈的元素的值
    const char* class_p = lua_tostring(L, stack_base + 1);
    char class_sz[32];
    strcpy(class_sz, class_p);
    int attack = lua_tointeger(L, stack_base + 2);
    int defense = lua_tointeger(L, stack_base + 3);
    // 清理栈
    lua_pop(L, 3);
    // 用这些值做一些操作
    printf("Character is %s with %d attack and %d defense\n", class_sz, attack,defense);
    // 关闭Lua 并清理
    lua_close(L);
    return 0;
}

上面的示例代码在任何内容被推送到栈之前获得了栈的基本索引,然后使用该基本索引的偏移量来读取数值。你永远不应该假设栈是空的——不要硬编码索引。相反,始终使用相对于已知偏移量的索引。

从 C 创建 Lua 变量

与 Lua 通信是一种方式。除了从C语言中读取Lua变量外,你还可以从C语言中创建Lua变量。这样做的过程很简单:你将一个值压入栈中,然后告诉Lua运行时把这个值按名字分配给一个变量。

要创建一个变量,请使用lua_setglobal (lua_State*, const char*)函数。这个函数不返回任何内容。它的第一个参数是要处理的Lua状态,第二个参数是要分配的全局变量的名称。这个函数将从栈中弹出顶部值,并将其分配给指定的变量名称。

让我们把上面的例子倒过来。这一次,变量class, attack, 和defense将在C语言中创建,并在Lua中打印出来。C语言代码将把所有的值推入栈,然后使用lua_setglobal把它们分配给变量。变量设置完毕后,应该加载并执行一个Lua文件:

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int main(int argc, char** argv) {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    // 压入值
    lua_pushstring(L, "Warrior");
    lua_pushnumber(L, 56);
    lua_pushnumber(L, 43);
    // 从栈顶分配,IE 逆序
    lua_setglobal(L, "defense");
    lua_setglobal(L, "attack");
    lua_setglobal(L, "class");
    // 加载文件 "printinfo.lua"
    int result = luaL_loadfile(L, "printinfo.lua");
    if (result != 0) {
        printf("Could not load hero.lua, exiting");
        lua_close(L);
    return -1;
    } 
    
    // 执行加载的 Lua 块
    result = lua_pcall(L, 000);
    if (result != 0) {
        const char* error = lua_tostring(L, -1);
        printf("Error loading hero.lua, exiting.\n");
        printf("Error message %s", error);
        lua_close(L);
        return -1;
    } 
    lua_close(L);
    return 0;
}

接下来,printinfo.lua文件将负责打印出所有这些数值。注意变量classattackdefense从未在 Lua中创建。但它们可以被引用,因为它们是在C语言中创建的:

print("Charater is " .. class .. " with " .. attack .. " attack and " .. defense .." defense");

从 C 调用 Lua 函数

从C语言调用Lua函数的方法已经在本文的加载Lua文件一节中讲过了,就是lua_pcall。这一次,我们将使用函数的第二个和第三个参数。提醒一下,第二个参数是栈中供Lua消耗的参数数量,第三个参数是我们期望Lua为我们留在栈中的值的数量。

让我们在 Lua 中创建一个示例函数,它接收两个数字并返回一个矩阵的线性索引。只有Lua代码会知道矩阵的宽度。用于查找此线性索引的 Lua 代码如下所示:

num_columns = 7
function GetIndex(row, col)
 return row * num_columns + col
end

上面的Lua函数期望在栈上有两个变量:rowcol。它将在栈中留下一个值。接下来,让我们创建一个具有类似名称和签名的C语言封装函数。这个包装函数被期望在一个有效的Lua上下文中运行,该上下文已经加载了 Lua 文件,该函数已经在前面的代码中定义:

int LinearIndex(lua_State*L, int row, int col) {
    // 将 GetIndex 函数压入栈
    lua_getglobal(L, "GetIndex");
    
    // Stack: function (GetIndex)
    // 将row变量压入栈
    lua_pushnumber(L, row);
    
    // Stack: function (GetIndex), int (row)
    // 将 col 变量压入栈
    lua_pushnumber(L, col);
    
    // Stack: function (GetIndex), int (row), int (col)
    // 从栈中弹出两个参数 (row & col)
    // 调用栈顶函数(GetIndex)
    // 在栈上留下一个值
    lua_pcall(L, 210);
    
    // Stack: int (return value of GetIndex)
    // 从栈中取出 GetIndex 的结果
    int result = lua_tointeger(L, -1);
    lua_pop(L, 1);
    // Stack: empty
    return result;
}

从 Lua 调用 C 函数

由于C和Lua中的函数工作方式不同,将C函数暴露给Lua可能会变得有点棘手。所有Lua可以调用的C函数必须遵循lua_CFunction的签名,它在lua.h中定义如下。

typedef int (*lua_CFunction) (lua_State *L);

这个函数只接受一个参数,即lua_State。该函数的返回值是一个整数。这个整数是函数作为返回值推入栈的元素的数量。

Lua有多个栈——从Lua调用的每个C函数都有自己的栈,不共享全局栈。

让我们以一个简单的C函数为例,它返回一个3 维向量的大小。在C语言中,代码如下:

double Vec3Magnitude(double x, double y, double z) {
    double dot = x * x + y * y + z * z;
    if (dot == 0.0) {
     return 0.0;
    }
    return sqrt(dot);
}

上面的函数不能直接暴露给Lua,因为它没有遵循lua_CFunction的签名。有两种方法可以暴露这个函数,要么重写它,要么为它写一个包装函数。这两种方法都是类似的。下面是一个重写的例子:

int LuaVec3Magnitude(lua_State* L) {
    double x = lua_tonumber(L, 3);
    double y = lua_tonumber(L, 2);
    double z = lua_tonumber(L, 1);
    
    lua_pop(L, 3);
    double dot = x * x + y * y + z * z;
    if (dot == 0.0) {
     lua_pushnil(L);
    }else {
     lua_pushnumber(L, sqrt(dot));
    } 
    return 1;
}

上面的函数可以从Lua中调用。在被调用之前,它必须被注册。注册一个函数意味着它首先需要被lua_pushcfunction函数推入栈。接下来,需要用lua_setglobal将栈中的函数分配给一个变量。下面的代码注册了LuaVec3Magnitude函数,使其在Lua中可用:

lua_pushcfunction(L, LuaVec3Magnitude);
lua_setglobal(L, "Vec3Magnitude");

LuaVec3Magnitude函数在Lua中被注册为Vec3Magnitude后,可以在任何时候被调用。

重写一个函数并不总是可能的,但你可以写一个包装函数。例如,我们可以创建一个名为LuaWrapperVec3Magnitude的函数,它不做Vec3Magnitude的工作,只是调用Vec3Magnitude函数。然后,我们可以将LuaWrapperVec3Magnitude作为Vec3Magnitude暴露给Lua。

下面的代码演示了这一点:

int LuaWrapperVec3Magnitude(lua_State* L) {
    double x = lua_tonumber(L, 3);
    double y = lua_tonumber(L, 2);
    double z = lua_tonumber(L, 1);
    lua_pop(L, 3);
    // 调用原来的函数,让它负责做实际的工作
    double result = Vec3Magnitude(x, y, z);
    if (dot == 0.0) {
     lua_pushnil(L);
    }else {
     lua_pushnumber(L, result);
    } 
    return 1;


// 暴露包装函数的代码。
lua_pushcfunction(L, LuaWrapperVec3Magnitude);
lua_setglobal(L, "Vec3Magnitude");

在C语言中使用表

到现在为止,我们只是在使用基本的Lua类型和函数。Lua的C语言API也允许我们与表一起工作。一个新的表可以通过lua_newtable (lua_State*)函数来创建。这个函数不返回任何东西,只接受Lua状态作为一个参数。lua_newtable函数将创建一个空表,并把它留在栈的顶部。一旦表在栈上,就由你来把它分配给一个变量。例如,以下代码创建了一个名为“vector”的具有全局作用域的表:

lua_newtable(L);
lua_setglobal(L, "vector");

在创建了表之后,你将能够从表中获取和设置数值。然而,要做到这一点,该表需要在栈中。可以使用 lua_getglobal 检索栈上的表,就像任何其他变量类型一样。

从表中读取值

表中的字段可以通过lua_gettable (lua_State*, int)函数来检索。这个函数不返回任何东西;它的第一个参数是要工作的Lua状态。通常情况下,在Lua中访问一个表涉及到表和一个键,例如:tbl[key]。使用 lua_gettable,表 (tbl) 应该位于第二个变量指定的索引处。键(key)应该在栈的顶部。下面的代码演示了如何从一个名为vector的表中检索出键x的值:

lua_getglobal(L, "vector");
lua_pushstring(L, "x");
lua_gettable(L, -2);

由于检索变量是如此普遍,Lua提供了一个方便的快捷函数,lua_getfield (lua_State*, int, const char*)。这个辅助函数避免了将键的名称推入栈,而是将其作为第三个参数。第二个参数仍然是栈中的表的索引。前面的例子可以用lua_getfield这样改写,如下:

// 将vector压入栈顶
lua_getglobal(L, "vector");
// 索引-1 指的是vector,它在栈顶
// 将 x 的值留在栈顶
lua_getfield(L, -1"x");
// 栈上有2个新的值(vector和x),需要在以下位置清理
some point

你可能已经注意到,前面的代码向lua_getfield传递了一个负数索引。回顾一下本文的查询栈部分,正数的索引是从下往上,而负数的索引是从上往下。

在前面的代码中传递-1是有效的,因为lua_getglobal函数调用会将 "vector"表留在栈的顶部。在这一点上,我们不知道(或关心)栈有多大,只是知道最上面的元素是 "vector"表。调用lua_getfield后,"x"的值就在栈顶。

将值写入表

Lua 提供了 lua_settable (lua_State*, int) 函数来设置一个表中的字段。这个函数不返回任何东西。它的第一个参数是要处理的Lua状态,第二个参数是栈中一个表的索引。

被设置的值应该在栈的顶部,而要设置的键应该就在它的下面。lua_settable将把键和值都从栈中弹出,但它会把表留在栈中。

例如,Lua代码vector["y"] = 7可以用这个API编写如下:

// 将向量压入栈
lua_gettable(L, "vector");
// 将 y 压入栈
lua_pushstring(L, "y");
// 将 7 压入栈
lua_pushnumber(L, 7);
// 栈上有三个新变量
// 7 的索引为 -1
// "y" 的索引为 -2
// "vector"的索引是-3
// 在索引 -3 处的"vector"表上调用lua_settable
lua_settable(L, -3);
// lua_settable 将键 ("y") 和值 (7) 从栈中弹出
// 栈中只剩下一项,即"vector"表
// 留在栈中的项应该在某个时候被清除

Lua还提供了lua_setfield (lua_State*, int, const char*)函数,它避免了将键推入栈的需要。前两个参数与lua_settable相同。第三个参数是被设置的键。

正在设置的值应位于栈顶部。lua_setfield函数将从栈中弹出该值,就像lua_settable所做的那样。

可以用 lua_setfield重写前面的代码示例,如下所示:

// 将向量压入栈
lua_gettable(L, "vector");
// 将 7 压入栈
lua_pushnumber(L, 7);
// 在索引 -2 处的"vector"表上调用 lua_setfield
lua_setfield(L, -2"y");
// lua_setfield 会将值 (7) 从栈中弹出
// 栈中只剩下一项,即"vector"表

元表

你可以通过int lua_getmetatable (lua_State*, int)函数来测试一个表是否有元表,并检索所述元表。这个函数的第一个参数是它所影响的Lua状态,第二个参数是栈中一个表的索引。如果指定索引处的表没有元表,lua_getmetatable函数返回0,并且不会向栈推送任何东西。如果指定索引处的表确实有一个元表,lua_getmetatable函数将返回1,并且将元表推入栈。

你可以用int lua_setmetatable (lua_State*, int)函数给一个现有的表分配一个元表。这个函数把它所影响的Lua状态作为它的第一个参数,并把栈中的表的索引作为第二个参数。它期望栈的顶部是元表,并将它从栈中弹出。如果它能分配元表,该函数将返回1。否则,如果发生错误,该函数将返回0。

User data

Lua有一个特殊的数据类型,叫做userdata。Userdata可以将任意的C语言数据结构作为Lua数据来存储——它只是一些任意的内存。Userdata可以有元表,这使得我们可以使用扩展表的相同机制来扩展该类型。像表一样,Userdata是通过引用来比较的,而不是通过值。

要创建一个新的userdata内存块,使用void* lua_newuserdata(lua_State*, size_t)函数。这个函数的第一个参数是要处理的Lua状态,第二个参数是要保留给用户数据的字节数。该函数返回一个指向Lua为该用户数据保留的内存块的指针。

一个三维向量可能会被存储在userdata中,如下所示:

struck Vec3 {
 float x, y, z;


int make_up_vector(lua_State *L) {
    Vec3* newVec = (Vev3*)lua_newuserdata(L, sizeof(Vec3));
    newVec->x = 0;
    newVec->y = 1;
    newVec->z = 0;
    //在栈上的新的用户数据
    return 1;
}

用户数据可以通过lua_touserdata函数来检索。这个函数返回一个指向用户数据存储器的指针。它的第一个参数是要处理的Lua状态,第二个参数是栈上的索引,用户数据应该在这个索引上。如果你修改了用户数据返回的指针,你就是修改了用户数据的实际值。下面的代码示例展示了如何使用lua_touserdata函数:

int lua_vec3_cross (lua_State *L) {
    Vec3* a = (Vec3*)lua_touserdata(L, -2);
    Vec3* b = (Vec3*)lua_touserdata(L, -1);
    
    float dot = a->x * b->x + a->y * b->y + a->z * b->z;
    lua_pushnumber(L, dot);
    return 1;
}

「更多详细的Lua C API信息,请查看 Lua语言的API手册[2]

本文介绍了Lua的C API的基本使用,下一篇文章中,将通过实例,重点介绍在LuaDardo库中,如何使用类似的API,实现Lua与Dart的相互调用。


编程之路从0到1

或关注博主的视频网校

云课堂

Reference

[1]

LuaDardo: https://github.com/arcticfox1919/LuaDardo

[2]

Lua语言的API手册: https://www.lua.org/manual/5.3/manual.html