lua如何调用C/C++
1 lua vs C/C++
lua是脚本语言,优点是门槛低,可以热更新,缺点当然就是性能。C/C++是编译型语言,有点是性能高,但是相对的,门槛高,技术不好的人写的代码可能还没有lua的性能高,容易出现core,不能热更新。
不过,lua语言本身就是用C实现的,而且,可以将很多能力封装成lua的接口供lua调用。
2 C/C++如何给lua提供接口
查看一个lua模块的源代码会发现,lua模块的实现中既包含lua代码,也包含C代码,其中,C代码的主要逻辑就是获取参数,调用系统调用,返回值,C代码会编译为so供lua调用,而lua代码就是将C代码提供的一些接口进行再封装,以便在lua中更好用,更简单,然后再通过lua代码对外提供接口。因此,这么看起来,通过lua调用C函数,重要的就是在C中如何获取参数以及如何返回值。
下面的说明以linotify项目进行说明。
2.1 lua模块的查找
当在lua里面通过require(“inotify”)时,lua怎么知道去哪里查找inotify模块呢?此时,inotify模块是个lua脚本还是个so呢?
跟其他脚本语言类似,lua中也是通过变量来控制模块的查找的,其中package.path是搜索lua模块的路径,package.cpath是搜索so模块的路径,先查找lua模块,再查找so模块。
通过上面这种方式,在当前目录找到了inotify.so。
2.2 so的入口
要调用inotify.so中的函数,肯定还是要用动态库的函数:dlopen、dlsym。例如,当调用require(“inotify”)时,如果没有导入inotify.so,则调用dlopen加载inotify.so库,
当在lua中调用local wd = handle:addwatch('/home/rob/', inotify.IN_CREATE, inotify.IN_MOVE)
时,会调用handle_add_watch()函数。
2.3 参数获取
lua和C之间是通过栈进行交互的,当调用C函数时,C函数的第一个参数是lua_State的指针,可以将它理解为lua的一个状态机。
如果要调用函数,第一步就是参数的获取,lua会将参数放到栈中,因此,inotify.so中的函数可以获取栈中的数据得到参数:
fd = get_inotify_handle(L, 1);path = luaL_checkstring(L, 2);top = lua_gettop(L);for (i = 3; i <= top; i++) {mask |= (uint32_t)luaL_checkinteger(L, i);}
get_inotify_handle()获取栈中的第1个参数,luaL_checkstring(L, 2)获取栈中的第2个参数,且第2个参数是个字符串,然后通过lua_gettop(L)获取所有的参数的个数,再用for循环将剩余的参数通过位或放到mask变量。通过这种方式就分别得到了addwatch()的三个参数。
然后再调用inotify的inotify_add_watch()完成实际的逻辑。
2.5 返回值
当具体的业务逻辑完成后,就需要将返回值传给lua,依旧是通过入栈的方式。
在这里,调用完inotify_add_watch()就得到某个监听操作的描述符,也需要将这个描述符返回,如果操作成功,调用lua_pushinteger(L, wd)
将wd返回,如果操作失败,则返回3个值:
static int handle_error(lua_State *L)
{lua_pushnil(L);lua_pushstring(L, strerror(errno));lua_pushinteger(L, errno);return 3;
}
第1个是nil,第2个是错误信息,第3个是错误码。
因此,在lua中可以这样来调用:
local wd, err_info, errno = handle:addwatch('/home/rob/', inotify.IN_CREATE, inotify.IN_MOVE)
if wd == nil thenprint("ERROR=", err_info)
end
同时还需要注意handle_add_watch()函数的返回值,返回值表明了lua中函数返回值的个数。例如这里,成功时,只返回描述符,因此,函数返回值是1,失败时,多了额外的错误信息,因此,函数返回值是3。
2.6 一个小的demo
有了上面的了解,可以实现我们的一个小小的demo。
假设我们要实现一个加法操作,实际的加法操作在C中完成,然后在lua中调用。
#include <lua.h>
#include <lauxlib.h>static int handle_add(lua_State *L) {int a, b, c;a = luaL_checkinteger(L, 1);b = luaL_checkinteger(L, 2);c = a + b;lua_pushinteger(L, c);return 1;
}static luaL_Reg funcs[] = {{"add", handle_add},{NULL, NULL}
};int luaopen_demo(lua_State *L) {lua_createtable(L, 0, sizeof(funcs)/sizeof(luaL_Reg) - 1);luaL_setfuncs(L, funcs, 0);return 1;
}
那这里的入口函数就是luaopen_demo(),里面就调用了两个函数,先调用lua_createtable创建
将上面的代码编译为so:
gcc demo.c -fPIC -shared -o demo.so
lua中调用:
local demo = require("demo")print(demo.add(2, 3))
3 lua FFI
lua C API实现lua的模块使用的是虚拟栈的方式,实现起来太过麻烦,用户需要使用一种新的接口(C API)和模式(虚拟栈)实现,而使用FFI机制,就可以在lua中直接调用C函数。
3.1 一个小例子
local ffi = require("ffi")
ffi.cdef[[
int printf(const char*fmt, ...);
]]ffi.C.printf("hello %s", "world");
首先加载ffi模块,然后使用cdef添加C函数的声明,有点类似于C语言中的头文件,然后就可以调用ffi.C中的printf函数。然后就可以使用luajit编译:luajit hell.lua
。
3.2 调用so
上面的例子是调用C标准库中的函数,如果需要调用其他的so文件呢?
// libtest.c
#include <stdio.h>int show(char *str) {int ret = 0;if(str == NULL) {ret = -1;} else {printf("input: %s\n", str);}return ret;
}
# 将上述代码编译为so
gcc -shared -fPIC libtest.c -o libtest.so
然后就可以在lua中调用:
local ffi = require("ffi")-- 加载libtest.so
local myffi = ffi.load("test")-- 声明函数原型
ffi.cdef[[
int show(char *str);
]]local str1 = "hello"-- 将字符串类型转换为char*
local str2 = ffi.cast("char *",str1)-- 调用libtest.so中的show函数
print(myffi.show(str2))
4 C API vs FFI
FFI相比C API最大的优势就是比较好理解,性能高,但是使用FFI也存在一些兼容性的问题;而C API由于是官方提供的接口,在稳定性方面还是很好的。
5 参考文档
- FFI Tutorial
相关文章:
lua如何调用C/C++
1 lua vs C/C lua是脚本语言,优点是门槛低,可以热更新,缺点当然就是性能。C/C是编译型语言,有点是性能高,但是相对的,门槛高,技术不好的人写的代码可能还没有lua的性能高,容易出现c…...

简单聊一聊公平锁和非公平锁,parallel并行流
目录 一、降低锁的粒度,将synchronized关键字不放在方法上了,改为synchronized代码块。二、先区分一下公平锁和非公平锁1、公平锁2、非公平锁3、公平锁的优缺点:4、非公平锁的优缺点: 三、是否对症下药四、IntStream.rangeClosed是…...

【SpringCloud】微服务技术栈入门4 - RabbitMQ初探
目录 RabbitMQ安装 rabbitmqSpringAMQP 基础队列WorkQueue路由发布订阅 FanoutExchangeDirectExchangeTopicExchange RabbitMQ 安装 rabbitmq 首先确保自己已经安装好了 docker 是 docker 拉取镜像文件:docker pull rabbitmq:3-management 拉取完毕,打…...
cefsharp(117.2.20)cef117.2.2最新体验版
一、下载nupkg https://www.nuget.org/packages/CefSharp.WinForms/ https://www.nuget.org/packages/CefSharp.Common/ https://www.nuget.org/packages/cef.redist.x64/ https://www.nuget.org/packages/cef.redist.x86/ 此版本暂时不支持H264。上一版本支持H264 cefsharp…...
layui在上传图片在前端处理图片压缩
有的人会遇到需要在前端代码处理图片压缩的问题,下面给大家分享怎么处理。 // 上传图片 var image_src var IsImgDealfalse; layui.upload.render({ elem: "#{tag}{id}", url: sessionStorage.getItem(httpUrlPrefix) /upload/uploadImage, // dataT…...

js 事件参考
事件参考 事件介绍 触发事件是为了通知代码可能影响代码执行的“有趣变化”。这些可能来自用户交互,例如使用鼠标或调整窗口大小,底层环境状态的变化(例如,低电量或来自操作系统的媒体事件)以及其他原因。 每个事件都由一个基于Event接口的…...

卷积网络的发展历史-LeNet
简介 LeNet是CNN结构的开山鼻祖,第一次定义了卷积神经网络的结构。 LeNet模型包含了多个卷积层和池化层,以及最后的全连接层用于分类。其中,每个卷积层都包含了一个卷积操作和一个非线性激活函数,用于提取输入图像的特征。池化层…...

(2023,GPT-4V,LLM,LMM,功能和应用)大型多模态模型的黎明:GPT-4V(ision) 的初步探索
The Dawn of LMMs: Preliminary Explorations with GPT-4V(ision) 公众号:EDPJ(添加 VX:CV_EDPJ 或直接进 Q 交流群:922230617 获取资料) 目录 0. 摘要 1. 简介 1.1 动机和概述 1.2 我们探索 GPT-4V 的方法 1.3…...

【C++设计模式之装饰模式:结构型】分析及示例
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许在运行时动态地给一个对象添加额外的行为。 描述 装饰模式通过创建一个包装器(Wrapper)来包裹原始对象,并在原始对象的行为前后添加额外的功能。…...
绘制散点图、曲线图、折线图和环形图失败, 设置迭代次数和进度无法保存图片
错误❌ 分别input设置(我想知道微积分的力量) 设1个人,他有每天3种方案,每天进步千分之一,千分之一,十万分之一等到他们迭代 200,500,1000,2000,3000,5000,9000次 他们在图片什么位置画曲线图࿰…...

micro-ROS中对消息的内存管理
文章目录 1.背景2.答案2.1.基本类型及其数组,不需要2.1.序列类型(复合类型、复合序列类型),需要 3.内存申请方法3.1.手动申请(Manual allocation)3.1.工具辅助(micro-ROS utilities)…...

Springboot中使用拦截器、过滤器、监听器
一、Servlet、Filter(过滤器)、 Listener(监听器)、Interceptor(拦截器) Javaweb三大组件:servlet、Filter(过滤器)、 Listener(监听器) Spring…...
代码随想录二刷day45
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、力扣70. 爬楼梯二、力扣322. 零钱兑换三、力扣279. 完全平方数 前言 一、力扣70. 爬楼梯 class Solution {public int climbStairs(int n) {int[] dp new…...

泊车功能专题介绍 ———— AVP系统基础数据交互内容
文章目录 系统架构系统功能描述云端子系统车辆子系统场端子系统用户APP 工作流程基础数据交互内容AVP 系统基础数据交互服务车/用户 - 云基础数据交互内容车位查询工作流程技术要求数据交互要求 车位预约工作流程技术要求数据交互要求 取消预约工作流程技术要求数据交互要求 泊…...

蓝桥杯每日一题2023.10.6
题目描述 门牌制作 - 蓝桥云课 (lanqiao.cn) 题目分析 #include<bits/stdc.h> using namespace std; int ans; int main() {for(int i 1; i < 2020; i ){int x i;while(x){int a x % 10;if(a 2)ans ;x / 10;}}cout << ans;return 0; } 题目描述 既约分数…...
7、【Qlib】【主要组件】Data Layer:数据框架与使用
7、【主要组件】Data Layer:数据框架与使用 简介数据准备Qlib 格式数据Qlib 格式数据集自动更新日频率数据将 CSV 格式转换为 Qlib 格式股票池(市场)多股票模式 数据API数据检索特征过滤器 数据加载器QlibDataLoaderStaticDataLoaderInterfac…...

Kubernetes安装部署 1
本文主要描述kubernetes的安装部署,kubernetes的安装部署主要包括三个关键组件,其中,包括kubeadm、kubelet、kubectl,这三个组件的功能描述如下所示: Kubeadm 用于启动与管理kubernetes集群 Kubelet 运行在所有集群的…...

在VS Code中优雅地编辑csv文件
文章目录 Rainbow csv转表格CSV to Tablecsv2tableCSV to Markdown Table Edit csv 下面这些插件对csv/tsv/psv都有着不错的支持,这几种格式的主要区别是分隔符不同。 功能入口/使用方法Rainbow csv按列赋色右键菜单CSV to Table转为ASCII表格指令CSV to Markdown …...
LCR 128.库存管理 I
题目来源: leetcode题目,网址:LCR 128. 库存管理 I - 力扣(LeetCode) 解题思路: 数组可以分割成两段的升序连续子数组,找到两个子数组的开始元素并返回较小者即可。 解题代码: …...
eigen::Affine3d 转换
平移eigen::vector3d和四元数Eigen::Quaterniond 转 eigen::Affine3d Eigen::Vector3d t Eigen::Vector3d::Zero(); Eigen::Quaterniond q Eigen::Quaterniond ::Identity();Eigen::Affine3d affine3d t * q.toRotationMatrix(); Eigen::Matrix4d 转 eigen::Affine3d Eige…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...

LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...