Lua与C语言的互相调用_c调用lua-程序员宅基地

技术标签: C API  Lua  Flutter  Dart与Flutter开发  LuaDardo  Dart  

本系列相关文章:
Flutter 热更新及动态UI生成
Lua 15分钟快速上手(上)
Lua 15分钟快速上手(下)
Lua与C语言的互相调用
LuaDardo中Dart与Lua的相互调用

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

在本文中,我们将重点探讨如何将Lua嵌入到C或C++应用程序中,作为胶水脚本存在。虽然此文只关注C语言的API,但在LuaDardo项目中,这些C API并没有做根本改变,只是在命名规范上做了适应Dart语言的变化(驼峰命名法)。简单说,LuaDardo基本是兼容这些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 表示栈顶正下方的元素,依此类推:

使用索引,你可以检查栈上任何元素的类型。这里列出的函数可以用来查询栈中元素的类型。它们都返回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, 0, 0, 0);
    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, 0, 0, 0);
    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, 2, 1, 0);
    
    // 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手册

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


关注公众号:编程之路从0到1

编程之路从0到1

或关注博主的视频网校

云课堂

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yingshukun/article/details/119544987

智能推荐

已知num为无符号十进制整数,请写一非递归算法,该算法输出num对应的r进制的各位数字。要求算法中用到的栈采用线性链表存储结构(1<r<10)。-程序员宅基地

文章浏览阅读74次。思路:num%r得到末位r进制数,num/r得到num去掉末位r进制数后的数字。得到的末位r进制数采用头插法插入链表中,更新num的值,循环计算,直到num为0,最后输出链表。//重置,s指针与头指针指向同一处。//更新num的值,至num为0退出循环。//末位r进制数存入s数据域中。//头插法插入链表中(无头结点)//定义头指针为空,s指针。= NULL) //s不为空,输出链表,栈先入后出。

开始报名!CW32开发者扶持计划正式进行,将助力中国的大学教育及人才培养_cw32开发者扶持计划申请-程序员宅基地

文章浏览阅读176次。武汉芯源半导体积极参与推动中国的大学教育改革以及注重电子行业的人才培养,建立以企业为主体、市场为导向、产学研深度融合的技术创新体系。2023年3月,武汉芯源半导体开发者扶持计划正式开始进行,以打造更为丰富的CW32生态社区。_cw32开发者扶持计划申请

希捷硬盘开机不识别,进入系统后自动扫描硬件以识别显示_st2000dm001不认盘-程序员宅基地

文章浏览阅读5.7k次。2014年底买的一块2TB希捷机械硬盘ST2000DM001-1ER164,用了两年更换了主板、CPU等,后来出现开机不识别的情况,具体表现为:关机后开机,找不到硬盘,就进入BIOS了,只要在BIOS状态下待机半分钟左右再重启,硬盘就会出现。进入系统后,重启(这个过程中主板对硬盘始终处于供电状态),也不会出现不识别硬盘的现象。就好像是硬盘或主板上某个电容坏了一样,刚开始给硬盘通电的N秒钟内电容未能..._st2000dm001不认盘

ADO.NET包含主要对象以及其作用-程序员宅基地

文章浏览阅读1.5k次。ADO.NET的数据源不单单是DB,也可以是XML、ExcelADO.NET连接数据源有两种交互模式:连接模式和断开模式两个对应的组件:数据提供程序(数据提供者)&DataSetSqlConnectionStringBuilder——连接字符串Connection对象用于开启程序和数据库之间的连接public SqlConnection c..._列举ado.net在操作数据库时,常用的对象及作用

Android 自定义对话框不能铺满全屏_android dialog宽度不铺满-程序员宅基地

文章浏览阅读113次。【代码】Android 自定义对话框不能铺满全屏。_android dialog宽度不铺满

Redis的主从集群与哨兵模式_redis的主从和哨兵集群-程序员宅基地

文章浏览阅读331次。Redis的主从集群与哨兵模式Redis的主从模式全量同步增量同步Redis主从同步策略流程redis主从部署环境哨兵模式原理哨兵模式概述哨兵模式的作用哨兵模式项目部署Redis的主从模式1、Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。2、为了分担读压力,Redis支持主从复制,保证主数据库的数据内容和从数据库的内容完全一致。3、Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。全量同步Redis全量复制一般发_redis的主从和哨兵集群

随便推点

mysql utf-8的作用_为什么不建议在MySQL中使用UTF-8-程序员宅基地

文章浏览阅读116次。作者:brightwang原文:https://www.jianshu.com/p/ab9aa8d4df7d最近我遇到了一个bug,我试着通过Rails在以“utf8”编码的MariaDB中保存一个UTF-8字符串,然后出现了一个离奇的错误:Incorrect string value: ‘😃 我用的是UTF-8编码的客户端,服务器也是UTF-8编码的,数据库也是,就连要保存的这个字符串“????..._mysql utf8的作用

MATLAB中对多张图片进行对比画图操作(包括RGB直方图、高斯+USM锐化后的图、HSV空间分量图及均衡化后的图)_matlab图像比较-程序员宅基地

文章浏览阅读278次。毕业这么久了,最近闲来准备把毕设过程中的代码整理公开一下,所有代码其实都是网上找的,但都是经过调试能跑通的,希望对需要的人有用。PS:里边很多注释不讲什么意思了,能看懂的自然能看懂。_matlab图像比较

16.libgdx根据配置文件生成布局(未完)-程序员宅基地

文章浏览阅读73次。思路:  screen分为普通和复杂两种,普通的功能大部分是页面跳转以及简单的crud数据,复杂的单独弄出来  跳转普通的screen,直接根据配置文件调整设置<layouts> <loyout screenId="0" bg="bg_start" name="start" defaultWinId="" bgm="" remark=""> ..._libgdx ui 布局

playwright-python 处理Text input、Checkboxs 和 radio buttons(三)_playwright checkbox-程序员宅基地

文章浏览阅读3k次,点赞2次,收藏13次。playwright-python 处理Text input和Checkboxs 和 radio buttonsText input输入框输入元素,直接用fill方法即可,支持 ,,[contenteditable] 和<label>这些标签,如下代码:page.fill('#name', 'Peter');# 日期输入page.fill('#date', '2020-02-02')# 时间输入page.fill('#time', '13-15')# 本地日期时间输入p_playwright checkbox

windows10使用Cygwin64安装PHP Swoole扩展_win10 php 安装swoole-程序员宅基地

文章浏览阅读596次,点赞5次,收藏6次。这是我看到最最详细的安装说明文章了,必须要给赞!学习了,也配置了,成功的一批!真不知道还有什么可补充的了,在此做个推广,喜欢的小伙伴,走起!_win10 php 安装swoole

angular2里引入flexible.js(rem的布局)_angular 使用rem-程序员宅基地

文章浏览阅读1k次。今天想实现页面的自适应,本来用的是栅格,但效果不理想,就想起了rem布局。以前使用rem布局,都是在原生html里,还没在框架里使用过,百度没百度出来,就自己琢磨,不知道方法规范不规范,反正成功了,操作如下:1、下载flexible.js2、引入到angular项目里3、根据自己的需要修改细节3.1、在flexible.js里修改每份的像素,3.2、引入cssrem插件,在设置里设..._angular 使用rem

推荐文章

热门文章

相关标签