Lua C接口编程(一)

news2025/1/23 22:28:00

引言

skynet 和 openresty 都是深度使用lua的典范,学习lua不经要学会基本语法,还要学会C语言与Lua交互。lua的一大优点就是能和c/c++无缝连接,而且可以在不需要重复编译c/c++的情况下可以修改lua文件并且起作用,当我们的项目文件很大的时候,使用lua进行项目修改极大的减少了等待时间。

Lua由标准C编写而成,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。

Lua C接口编程我会分为两个部分来介绍,第一部分介绍C/C++如何调用Lua,第二部分介绍Lua如何调用C/C++。

一、Lua虚拟栈

  • 栈中只能存放Lua类型的值,如果想保存C类型的值,需要先将C类型转换为Lua类型;
  • Lua每次调用C函数都会生成新的栈,独立于之前的栈
  • C在创建虚拟机是,伴随创建一个主协程和一个虚拟栈
  • 无论何时Lua再调用C时,只保证至少有LUA_MINSTACK大小的堆栈空间可以使用。LUA_MINSTACK一般定义为20,只要你不是一致压栈,通常不用担心堆栈大小。

Lua中的栈有两排索引,正数1索引的位置在栈底,负数索引-1在栈顶,这样做的好处是不需要知道栈的大小,只需要查找正负索引1的位置就能确定栈顶和栈底的位置。
在这里插入图片描述
注意:C 在调用lua的时候要注意虚拟堆栈的变化情况,要保持堆栈在函数执行前后不变,养成良好的习惯。

二、注册表

2.1 什么是注册表

有时候,我们需要在程序中使用一些非局部的变量。在C中我们可以使用全局变量或是静态变量来实现,而在为Lua编写C库的过程中,使用以上类型的变量并不是一个好的方式,原因如下:

  1. 这些变量中无法存储Lua的值
  2. 这些变量如果在多个Lua虚拟机中被使用,很可能造成不可预期的结果。

一个替代方案是,将这些值存储在Lua的全局变量中。这种方式解决了上面提到的两个问题,Lua全局变量可以存储任何Lua的值,同时每一个Lua状态机都有自己独立的一套全局变量。但这依旧不是最好的方式,因为是Lua的全局变量,Lua程序可以随意的修改变量的值,这很可能对C库中的函数在使用这些变量时造成影响。

为了进一步避免上述情况,Lua提供了一张特殊的表(table),它可以供C代码随意使用。但是对于Lua代码,访问却是被禁止的。这个特殊的表便是注册表。

2.2 伪索引 pseudo-index

伪索引跟虚拟栈中正常的索引类似,区别在于,虽然使用它也是通过虚拟栈,但其所对应的值并不是存储在虚拟栈中。比如:LUA_REGISTRYINDEX 就是一个伪索引,定义在”lua.h”中。它用于通过虚拟栈访问注册表(但注册表并非实际存储在虚拟栈中),使用时按照虚拟栈中正常索引的使用方式使用。

2.3 小结

用来在多个C库中共享Lua数据,包括userdata 和 lightuserdata等;

  • 一张预定义的表,用来保存任何C代码想保存的Lua值;
  • 使用 LUA_REGISTRYINDEX来索引;
  • 比如:获取skynet_context

三、Lua 常用API介绍

lua_State* L=luaL_newstate(); -- 函数返回一个指向堆栈的指针
luaL_openlibs(L);        -- 打开指定状态机中的所有 Lua 标准库,也就是把所有标准类库加载到指定的虚拟机.
lua_createtable(L,0,0);  -- 新建并压入一张表
lua_pushstring(L,0,0);  -- 压入一个字符串
lua_pushnumber(L,0,0);  -- 压入一个数字
lua_tostring(L,1);  	-- 取出一个字符串
lua_tointeger(L,1); 	-- 取出数字

double b =lua_tonumber();  -- 取出一个double类型的数字
lua_load()函数   			-- 当这个函数返回0时表示加载
luaL_loadfile(filename)    -- 这个函数也是只允许加载lua程序文件,不执行lua文件。它是在内部去用lua_load()去加载指定名为filename的lua程序文件。当返回0表示没有错误。
luaL_dofile()  			   -- 这个函数不仅仅加载了lua程序文件,还执行lua文件。返回0表示没有错误。
lua_push*(L,data)          -- 压栈,
lua_to*(L,index)           -- 只取值, 不出栈
lua_pop(L,count)           -- 出栈。
lua_close(L);              -- 释放lua资源
lua_call(L,0,0,0);         -- 调用对应函数
lua_pcall(L,0,0,0);        -- 安全的调用对应函数,最后一个参数是错误处理函数

除了上面的API,我们着重看下面几个API:

  1. lua_getglobal(L, funcname)
    lua_getglobal是个宏,每次调用这个宏的时候,都会将Lua代码中funcname与之相应的全局变量值压入栈中。
  2. lua_setfield(L, LUA_REGISTRYINDEX, “LUA_NOENV”);
    等价于执行 LUA_REGISTRYINDEX[LUA_NOENV] = stackTopElem 操作, 其中 stackTopElem指当前栈顶元素。
  3. lua_getfield(L, LUA_REGISTRYINDEX, “LUA_NOENV”);
    将LUA_REGISTRYINDEX[LUA_NOENV] 压入栈,返回入栈值的类型
  4. int luaL_ref(lua_State L, int t)
    将栈顶的元素出栈作为value,从虚拟栈的索引 t 处获取到 table ,之后相当于执行"table[key] = value"的操作。函数返回生成的唯一的key,程序可以通过该key获取到对应的value。如果 value 为 nil ,则不会生成key,函数返回 LUA_REFNIL。
  5. void luaL_unref(lua_State L, int t, int ref);
    从虚拟栈的索引 t 处获得 table,之后做等价于执行 table[ref] = nil 操作。如果 ref 是LUA_NOREF或LUA_REFNIL,则函数不做任何操作。
  6. lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
    从注册表通过key(ref)获取到对应的value(foo函数),并将其入栈

使用 luaL_ref 和 luaL_unref 时无需关心如何创建唯一的”key”,便可以在注册表中自由的存取数据。

代码示例

示例1:常用api用法

C源文件test_field.c

#include <stdio.h>
#include <stdlib.h>

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

/*
    Lua的异常捕获主要基于pcall及xpcall函数。
*/

void getStackSize(const char * desc, const int count)
{
    printf("%s stack size = [ %d ]\n",desc, count);
}

void test_api_getfield()
{
    lua_State *L = luaL_newstate();

    // 加载并执行目标lua文件
    if ( LUA_OK != luaL_dofile(L, "test_field.lua") ) {
        const char* err = lua_tostring(L, -1);
        fprintf(stderr, "err:\t%s\n", err);
        return ;
    }

    lua_getglobal(L,"tab");           // 查找 tab 变量压入栈底

    // lua_gettop 获取栈中元素数量
    getStackSize("stage 1", lua_gettop(L));

    // 
    lua_getfield(L, -1, "a");           // 将 tab.a 入栈
    int nTab_a = lua_tointeger(L,-1); // 将 tab.a 取出赋值给变量nTab_a

    lua_getfield(L, -2, "b");           // 将 tab.b 入栈
    int nTab_b = lua_tointeger(L,-1); // 将 tab.b 取出赋值给变量nTab_b
    getStackSize("stage 2",lua_gettop(L));

    lua_pop(L, 3);                  // 清除掉栈中多余的3个变量tab、tab.a、tab.b
    getStackSize("stage 3",lua_gettop(L));

    int nTab_c = 2 * nTab_a + nTab_b;
    lua_pushinteger(L, nTab_c);       // 将 c = 2a + b 计算完成,压入栈顶
    printf("nTab_c = %d \n", nTab_c);  // 输出: 5
    
    getStackSize("stage 4",lua_gettop(L));

    lua_getglobal(L,"lua_func");        // 查找lua_func函数并将其压入栈底
    lua_pushinteger(L, 3);              // 压入函数变量 x=3  
    getStackSize("stage 5",lua_gettop(L));

    // lua_pcall 在保护模式下调用一个函数,防止异常程序直接退出
    lua_pcall(L,1,1,0);             // 执行脚本函数lua_func

    getStackSize("stage 6",lua_gettop(L));

    int result = lua_tointeger(L,-1);   // 从栈中取回返回值 
    getStackSize("stage 7",lua_gettop(L));
    printf("res = %d \n", result);  


    lua_pop(L,1);                       // 弹出返回结果
    getStackSize("stage 8",lua_gettop(L));

    lua_close(L);                       //关闭lua环境  
}

int main(int argc, char** argv)
{
    test_api_getfield();
    return 0;
}

lua文件test_fielf.lua

-- tab 表
tab =
{
    a = 2,
    b = 1
}

-- 全局变量c
c = 100;

function lua_func(x)
    -- print("lua c: ", c)
    return (tab.a * x * x + tab.b * x + c)
end

编译方法:
gcc -g -o test_field test_field.c -llua -ldl -lm

运行结果:
在这里插入图片描述

示例2:luaL_ref 、luaL_unref、lua_rawgeti 应用

C源文件test_reg_ref.c

#include <stdlib.h>

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

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

    if ( LUA_OK != luaL_dofile(L, "test_reg_ref.lua") ) {
        const char* err = lua_tostring(L, -1);
        fprintf(stderr, "err:\t%s\n", err);
        return 1;
    }

    lua_getglobal(L,"foo");
    printf("stack 1 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));

    // 从堆栈顶弹出foo函数,存放到注册表中并返回引用
    // luaL_ref 返回一个int的值。这个返回值就是对应的foo函数的 key  
    int ref =  luaL_ref(L, LUA_REGISTRYINDEX);
    printf("stack 2 size : %d\n", lua_gettop(L));

   // lua_rawgeti(L,LUA_REGISTRYINDEX,ref); 可以从注册表通过key(ref)获取到对应的value(foo函数)
   // 将该函数放入堆栈
    lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
    printf("stack 3 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));

    // 调用foo函数后 foo出栈
    lua_pcall(L,0,0,0);    
    printf("stack 4 size : %d\n", lua_gettop(L));

    printf("----------------分割线 1------------------\n");
    // 继续将foo压入堆栈
    lua_getglobal(L,"foo");  
    printf("stack 5 size : %d\n", lua_gettop(L));


    lua_setfield(L, LUA_REGISTRYINDEX, "fooKey");  //  相当于:LUA_REGISTRYINDEX[fooKey] = foo;
    printf("stack 6 size : %d\n", lua_gettop(L));

    lua_getfield(L, LUA_REGISTRYINDEX, "fooKey");  // 通过 fooKey 从 LUA_REGISTRYINDEX拿到foo 并入压栈
    printf("stack 7 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));

    lua_pcall(L,0,0,0);        // foo call
    printf("stack 8 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));


    printf("----------------分割线 2------------------\n");
    
    // 一旦ref在注册表的引用解除,就无法继续通过key(ref)这个引用获取到 value(即foo函数)

    luaL_unref(L, LUA_REGISTRYINDEX, ref);


    // 从注册表解除后,lua_rawgeti无法再通过key(ref) 找到foo
    lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
    printf("stack 9 size : %d, %d\n", lua_gettop(L), lua_type(L,-1));

    // 通过 fooKey 还是可以继续从LUA_REGISTRYINDEX拿到foo 并入压栈
    lua_getfield(L,LUA_REGISTRYINDEX,"fooKey");
    printf("stack 10 size : %d\n", lua_gettop(L));

    lua_pcall(L,0,0,0);

    printf("---------------- end -----------------\n");
    lua_close(L);
   return 0;
}

Lua文件test_reg_ref.lua

function foo()
    print("test_reg_ref foo call")
end

编译方法:
gcc -g -o test_reg_ref test_reg_ref.c -llua -ldl -lm
运行结果:
在这里插入图片描述
推荐一个零声学院免费教程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/151306.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【面试题】做了一份前端面试复习计划,保熟~

大厂面试题分享 面试题库前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库前言以前我看到面试贴就直接刷掉的&#xff0c;从不会多看一眼&#xff0c;直到去年 9 月份我开始准备面试时&#xff0c;才发现很多面试经验贴…

Kubernetes(k8s) 笔记总结(二)

提示&#xff1a;针对kubernetes的工作均衡学习。 文章目录1. Kubernetes 创建资源方式2. Kubernetes 操作NameSpace3. Kubernetes的 Pod应用3.1 Pod的 解释3.2 通过命令行来创建一个pod3.3 配置文件方式创建一个Pod3.4 dashboard 可视化操作Pod3.5 针对Pod的一些细节操作3.6 P…

如何评估PMO (项目管理办公室)的实施效果?

使用有效的组织战略、方法和技术&#xff0c;可以成功启动并制度化企业范围的PMO (项目管理办公室)。 一个企业范围内的PMO可以使用成熟的技术启动。 但你应该开发和使用适当的评估工具&#xff0c;以确定你的PMO实施的项目管理过程的有效性。这些工具可以包括正式的管理评估…

黑马学ElasticSearch(四)

目录&#xff1a; &#xff08;1&#xff09;RestClient操作文档-新建文档 &#xff08;2&#xff09;RestClient操作文档-查询文档 &#xff08;3&#xff09;RestClient操作文档-更新文档 &#xff08;4&#xff09;RestClient操作文档-删除文档 &#xff08;5&#xff…

包管理工具详解npm 、 yarn 、 cnpm 、 npx 、 pnpm

1、包管理工具npm &#xff08;1&#xff09;包管理工具npm&#xff1a; Node Package Manager&#xff0c;也就是Node包管理器&#xff1b;但是目前已经不仅仅是Node包管理器了&#xff0c;在前端项目中我们也在使用它来管理依赖的包&#xff1b;比如vue、vue-router、vuex、…

数据分析-深度学习 Day4

本专栏主本专栏主要介绍和讲解李宏毅老师最新2021春季机器学习课程相关内容&#xff0c;如有记录错误&#xff0c;或理解不对&#xff0c;欢迎留言批评指正...youtube课程地址&#xff1a;&#xff08;实时更新&#xff09;ML 2021 Springspeech.ee.ntu.edu.tw/~hylee/ml/2021-…

sxs卡数据怎么恢复?分享三种恢复方案

说起sxs卡&#xff0c;你们是否有所了解呢&#xff1f;sxs卡具有很好的传输性能&#xff0c;能够存储照片和视频数据&#xff0c;主要被放置在索尼XDCAM EX型摄像机上。而在使用sxs卡设备过程中&#xff0c;难免和其他设备一样&#xff0c;容易出现数据丢失情况。而如果丢失的是…

在虚拟机上安装win11

虚拟机版本呢vmware16win11镜像&#xff1a;zh-cn_windows_11_business_editions_version_22h2_updated_dec_2022_x64_dvd_0b26ca48.isoPE镜像&#xff1a;EasyU_v3.7.iso 通过优启通工具制作两个光驱&#xff0c;第一个选pe的iso&#xff0c;第二个选win11的iso点击开启虚拟机…

(day5) 自学Java——ArrayList集合

目录 1. ArrayList 2.集合练习 (1)添加字符串和整数&#xff0c;并遍历 (2)添加学生对象并遍历 (3)查找用户是否存在 (4)返回多个数据 1. ArrayList 数组有个致命的弱点&#xff0c;那就是创建时需要指定其长度&#xff0c;并且在使用时长度不可改变。 在Java教程中知道…

[ 数据结构 ] 图(Graph)--------深度优先、广度优先遍历

0 基本介绍 为什么要有图? 无论是线性表还是树结构,局限于表示一个直接前驱和一个直接后继的关系(一对一/一对多),当我们需要表示多对多的关系时&#xff0c; 这里我们就用到了图 节点间的连接成为边,节点称为顶点,一个顶点到另一个顶点所经过的边叫路径,边有方向的叫有向图,…

js逆向-某动网演出数据获取

声明 本文仅供学习参考&#xff0c;如有侵权可私信本人删除&#xff0c;请勿用于其他途径&#xff0c;违者后果自负&#xff01; 如果觉得文章对你有所帮助&#xff0c;可以给博主点击关注和收藏哦&#xff01; 前言 目标网站&#xff1a;aHR0cHM6Ly93d3cuc2hvd3N0YXJ0LmNv…

如何远程连接Linux系统服务器

Linux服务器远程连接方法这里给大家普及一下Linux服务器&#xff0c;是一台安装Ubuntu系统的工作站。这类服务器大部分用于程序员开发编程使用&#xff0c;说简单点就是拿来敲代码的。通常需要借助远程连接工具来连接Linux远程服务器&#xff0c;如xshell&#xff1b;putty&…

PostgreSQL实战之物理复制和逻辑复制(五)

目录 PostgreSQL实战之物理复制和逻辑复制&#xff08;五&#xff09; 5 流复制主备切换 5.1 判断主备角色的五种方法 5.2 主备切换之文件触发方式 5.3 主备切换之pg_ctl promote方式 5.4 pg_rewind PostgreSQL实战之物理复制和逻辑复制&#xff08;五&#xff09; 5 流…

Vue作业

文章目录作业1作业2作业3作业4作业1 作业&#xff1a;需要用data保存&#xff1a;商品名、单价、数量&#xff0c;然后显示到页面上&#xff0c;点击按钮可以变更数量&#xff0c;最小值1&#xff0c;减按钮不可用&#xff0c;最大值20&#xff0c;隐藏按钮 总价格应该是 单价…

酒业“新物种”的新思维:用户、品牌、模式

【潮汐商业评论/原创】中国的酒文化源远流长。古人有一万种喝酒的理由&#xff0c;或聚会畅饮&#xff0c;或独酌解忧&#xff0c;而餐桌是酒最常出现的地方。如今&#xff0c;与酒相关的场景日益多元&#xff0c;往往洋溢着青春的气息。一顶帐篷&#xff0c;三两好友围坐&…

一、数据仓库基础理论

数据仓库基础理论一、数据仓库1、概念2、数据仓库分层结构3、为什么要分层二、数据集市三、数据湖1、数据湖和数据仓库一、数据仓库 1、概念 数据仓库&#xff08;Data Warehouse, DW&#xff09;&#xff1a;一个面向主题的、集成的、非易失的、反应历史变化的、用来支持企业…

哈弗品牌发布新能源越野新品类车型:H-DOG

1月6日&#xff0c;哈弗品牌新能源车型哈弗H-DOG在海口新能源车展亮相。哈弗H-DOG针对用户5天通勤、2天郊野、e享时刻三大使用场景打磨&#xff0c;满足用户“可城、可野、可电”的用车需求&#xff0c;是哈弗品牌在新能源轻越野上的全新探索。• 哈弗H-DOG采用了全新的潮野力量…

Sentinel降级策略-RT、异常比例、异常数

Sentinel降级—RT 1&#xff09;RT&#xff1a;平均响应时间&#xff1b;配置平均响应时间为200ms&#xff0c;200ms内就要将请求处理完成&#xff1b; 2&#xff09;注意&#xff1a;Sentinel 默认统计的 RT 上限是4900ms&#xff0c;超出此阈值会算作 4900ms &#xff0c;若…

【PyTorch深度学习实践】05_逻辑斯蒂回归

文章目录1. 分类问题相关数据集1.1 MINIST1.2 CIFAR-102. 回归(Regression)VS分类(Classification)2.1 模型对比2.2 损失函数对比2.3 实现代码对比3. 完整代码之前使用线性回归解决的都是**回归&#xff08;预测&#xff09;**问题&#xff0c;逻辑斯蒂回归模型可以用来解决另一…

MySQL数据库:数据基本的增删改查

一、查询数据 1.查询表内所有数据 select * from 表名; 2.指定列查询 select 字段1, 字段2, …… from 表名; 3.查询字段为表达式 select 表达式1, 表达式2,…… from 表名; 4.起表名查询 如果对查询结果的字段名不满意&#xff0c;还可以自己进行取别名。 select 字段1 …