当前位置:

首页 > 编程开发 > lua调用C/C++的方法详解

lua调用C/C++的方法详解

Lua通过虚拟栈与C/C++交互,将C函数封装为接口供其调用。调用时,C从栈中获取参数并返回结果,参数传递与结果返回均通过栈操作完成,从而实现了Lua对C能力的高效、便捷调用,使得Lua能无缝利用C语言的高性能库。

本文目录

    1 lua vs C/C++

    Lua 和 C/C++ 的取舍,其实是一道经典的选择题。Lua 作为脚本语言,上手门槛低,支持热更新,开发效率高——但代价是性能。C/C++ 编译型,性能是它的底气,可门槛也摆在那里:写不好的人,产出的代码可能还不如 Lua 快,而且动不动就 core dump,更别提热更新了。

    不过话说回来,Lua 本身就是用 C 实现的,这套血缘关系让两者之间打通“任督二脉”并不复杂——只需要把 C 的能力封装成 Lua 能调用的接口就行。

    lua调用C/C++的方法详解

    2 C/C++如何给lua提供接口

    翻看一个典型 Lua 模块的源代码会发现,它往往既包含 Lua 代码也包含 C 代码。C 代码负责干重活:获取参数、调用系统 API、返回结果,然后编译成 .so 供 Lua 调用。Lua 代码则对 C 暴露出来的底层接口进行再封装,让调用更顺手。所以,Lua 调用 C 函数的关键,就集中在 C 函数里如何接收参数、如何返回结果上。

    下面以 linotify 项目为例来拆解这个过程。

    2.1 lua模块的查找

    当你在 Lua 里写下 require("inotify") 时,它怎么知道上哪儿找这个模块?是 .lua 脚本还是 .so 动态库?

    和其他脚本语言类似,Lua 通过变量来控制搜索路径:package.path 负责 Lua 脚本,package.cpath 负责 .so 动态库。搜索顺序是:先找 Lua 脚本,再找 .so。顺着这个流程,它在当前目录找到了 inotify.so。

    2.2 so的入口

    调用 inotify.so 里的函数,本质还是动态库那套机制:dlopen 装载,dlsym 定位函数。当执行 require("inotify") 时,如果还没加载过这个 .so,Lua 会先调用 dlopen 把它拉进内存。

    随后,Lua 中执行 local wd = handle:addwatch('/home/rob/', inotify.IN_CREATE, inotify.IN_MOVE) 时,实际触发的是 C 函数 handle_add_watch()。

    2.3 参数获取

    Lua 和 C 之间通过虚拟栈(virtual stack)来传递数据。C 函数拿到的第一个参数是 lua_State * 指针——可以把它理解成 Lua 虚拟机的状态句柄。

    调用 C 函数的第一步,就是从栈里取出参数。举个例子,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(L, 1) 取栈中第一个参数(自定义句柄),luaL_checkstring(L, 2) 取第二个参数,并校验它是字符串。然后通过 lua_gettop(L) 获取参数总数,循环处理剩下的参数,依次取整数并通过位或合并到 mask 中。这样就把 addwatch() 的三个参数全部剥离出来了。之后调用真正的系统接口 inotify_add_watch() 完成监听注册。

    2.4 返回值

    业务逻辑跑完,怎么把结果传回 Lua?还是走栈,只不过这次是入栈。

    比如 inotify_add_watch() 返回一个监听描述符 wd。成功时,调用 lua_pushinteger(L, wd) 入栈;失败时,则返回三个值:

    static int handle_error(lua_State *L)
    {
        lua_pushnil(L);
        lua_pushstring(L, strerror(errno));
        lua_pushinteger(L, errno);
        return 3;
    }

    第一个是 nil,第二个是错误描述字符串,第三个是错误码。所以 Lua 端的调用就可以写成:

    local wd, err_info, errno = handle:addwatch('/home/rob/', inotify.IN_CREATE, inotify.IN_MOVE)
    if wd == nil then
        print("ERROR=", err_info)
    end

    注意 C 函数 handle_add_watch() 最后的返回值——它告诉 Lua 这个函数返回了几个值。成功时返回 1(只有描述符),失败时返回 3(nil、错误信息、错误码)。

    2.5 一个小的demo

    有了上面的基础,自己动手写一个简单的示例吧。假设我们要实现一个加法:实际计算在 C 里完成,Lua 只管调用。

    #include 
    #include 
    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))

    输出 5,完美。

    3 lua FFI

    用 C API 写 Lua 模块,需要你熟悉虚拟栈这套模式,写起来多少有些别扭。FFI(Foreign Function Interface)提供了一种更直接的方式——你可以在 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

    上面调用的是标准库函数,如果想调用自己编译的 .so 呢?

    先写一个简单的 C 文件 libtest.c:

    // libtest.c
    #include 
    int show(char *str) {
    	int ret = 0;
    	if(str == NULL) {
    		ret = -1;
    	} else {
    		printf("input: %sn", str);
    	}
    	return ret;
    }

    编译成 .so:

    gcc -shared -fPIC libtest.c -o libtest.so

    然后在 Lua 中通过 FFI 加载并调用:

    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))

    注意这里需要 ffi.cast 类型转换,因为 Lua 的字符串和 C 的 char * 并非直接等价。

    4 C API vs FFI

    FFI 最大的优势是直观、性能高(尤其是 LuaJIT 下的优化),但兼容性是个问题——它高度依赖 LuaJIT,标准 Lua 解释器并不原生支持。而 C API 是 Lua 官方提供的标准接口,稳定性久经考验,不挑运行环境。怎么选,取决于你的项目对性能和跨平台可靠性的权重。

    5 参考文档

    • FFI Tutorial
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    编程语言的编程模型
    编程语言的编程模型

    编程模型涵盖面向过程的C语言、面向对象的Ruby、基于栈的PostScript、基于规则的Prolog、基于原型的Lua、函数式的Erlang/Haskell及混合模型的Scala/Clojure。类型系统区分强类型与弱类型、动态类型与静态类型,元编程允许运行时操作代码,提升表达力。

    lua 如何获取指定路径下的所有文件夹
    lua 如何获取指定路径下的所有文件夹

    Lua编程语言轻量级且小巧,可用io.popen执行系统命令获取指定路径下所有文件夹:在Windows系统下使用dir/b/s命令,在Unix系统下使用find-typef命令。然而官方更推荐使用lfs库进行递归遍历,通过lfs.dir和lfs.attributes函数筛选目录,此法避免系统命令依赖,代码简洁且跨平台效果更佳。

    详解Lua中的元表和元方法
    详解Lua中的元表和元方法

    一、元表 元表是Lua中一个挺有意思的机制——它允许我们在一个值遇到未知操作时,给出自定义的行为。本质上,元表也是用table来承载的,所以熟悉了table,也就掌握了元表的基本用法。 不过得注意,元表只能针对一组预先定义好的操作集合做扩展,比面向对象中的类要受限得多。最典型的一点:元表不支持继承。

    如何使用Vim搭建Lua开发环境详解
    如何使用Vim搭建Lua开发环境详解

    基于SpaceVim搭建Lua开发环境,需先安装Neovim、Nerd字体及支持真色的终端,启用lang#lua模块后,即可获得全面的代码补全、语法检查、工程文件跳转、一键运行、代码格式化及交互式编程等高效开发功能,大幅提升编程效率与体验。

    Luvit像Node.js一样写Lua应用
    Luvit像Node.js一样写Lua应用

    先问个问题:如果你是一个Lua开发者,手头刚好有异步I/O的需求,你会怎么办?传统方案当然有,但要说最接近Node.js体验的,还得是Luvit。简单来说,它就是Lua界的Node.js——把Lua和libUV撮合在一起,再加上JIT加持,用项目自己的话说:Lua + libUV + JIT = p

    Lua流程控制语句if else的使用示例
    Lua流程控制语句if else的使用示例

    Lua的流程控制,if和if else是基础。和C语言很像,if语句支持嵌套,也能和循环配合——比如在循环体内根据条件决定是否跳出,这在日常开发中非常实用。 1、if语句 先看最基本的if语法结构: if(布尔表达式) then --[ 在布尔表达式为 true 时执行的语句 --] end 来看一

    Lua中pairs与ipairs的区别总结
    Lua中pairs与ipairs的区别总结

    最近在写一个流量转发的东西,用到了 nginx_lua_module 这个模块,需要根据 Header、Body 和 Cookie,按照流量比例把请求转发到其他地方。翻看前人留下的代码,发现循环遍历时,有时候用 pairs,有时候用 ipairs,风格并不统一,读起来让人有点困惑。 好在 Lua 官

    Lua游戏开发教程之时区问题详解
    Lua游戏开发教程之时区问题详解

    通过Lua的os.date("!*t")获取UTC时间,结合服务器时区差值,可将游戏内时间统一显示为服务器所在地时间。计算活动倒计时时,需考虑本地时区与服务器时区的差异,转换时间戳后得到准确剩余时间。正确处理时区问题能解决多语种发行中的显示同步难题。

    深入谈谈lua中神奇的table
    深入谈谈lua中神奇的table

    Lua中的table可作为字典、数组、环境、模块、对象和类使用。字典支持任意非nil键类型,遍历无序;数组索引从1开始,#对空洞处理需谨慎。table掌管全局环境_G和局部环境_ENV,模块本质为table。对象通过闭包实现私有成员,冒号语法糖简化self传递;类借助元表__index实现继承。

    利用Lua定制Redis命令的方法详解
    利用Lua定制Redis命令的方法详解

    Redis单线程但多客户端无同步导致数据冲突,网络往返也制约效率。通过内嵌Lua脚本可将多个命令复合执行,实现原子操作并减少请求次数。需注意脚本中避免全局变量、控制耗时,且报错时已执行命令无法回滚。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。