当前位置: 首页 > 工具软件 > LuatOS > 使用案例 >

LuatOS开发之自定义C库 C与Lua调用

董谦
2023-12-01

一、前言

今天继续讲LuatOS的开发,上一期简单说了一下如何移植LuatOS,相信很多朋友已经看过了,那么今天,我就开始讲C和Lua调用的部分教程。

二、闲谈C与Lua的调用

1、先来个中文文档

https://www.runoob.com/manual/lua53doc/contents.html

2、简介

Lua 使用一个虚拟栈来和C互传值,也就是说,C和Lua的数据交互是在栈上进行的。

无论何时 Lua 调用 C,被调用的函数都得到一个新的栈, 这个栈独立于 C 函数本身的栈,也独立于之前的 Lua 栈。 它里面包含了 Lua 传递给 C 函数的所有参数, 而 C 函数则把要返回的结果放入这个栈以返回给调用者 。(关键词:入栈,压栈)

栈:要是你没接触过,就当他是个先入后出的大袋子,你要做的就是往里面放东西和拿东西。

C和Lua的调用说大白话就是:在栈上操作。

3、常用函数

lua_State :lua 虚拟机中的环境表、注册表、运行堆栈、虚拟机的上下文等数据。会在写函数的时候首先使用

常用判断函数:

lua_isboolean : lua_isboolean (lua_State *L, int index); 当给定索引的值是一个布尔量时,返回 1 ,否则返回 0 。

lua_isinteger: lua_isinteger (lua_State *L, int index); 当给定索引的值是一个整数时,返回 1 ,否则返回 0 。

lua_isnil : lua_isnil (lua_State *L, int index); 当给定索引的值是 nil 时,返回 1 ,否则返回 0 。

lua_isnumber: lua_isnumber (lua_State *L, int index); 当给定索引的值是一个数字,或是一个可转换为数字的字符串时,返回 1 ,否则返回 0 。

lua_isstring : lua_isstring (lua_State *L, int index); 当给定索引的值是一个字符串或是一个数字( 数字总能转换成字符串)时,返回 1 ,否则返回 0 。

lua_istable : lua_istable (lua_State *L, int index); 当给定索引的值是一张表时,返回 1 ,否则返回 0 。

常用检查函数:

luaL_checkinteger : luaL_checkinteger (lua_State *L, int arg); 检查函数的第 arg 个参数是否是一个 整数(或是可以被转换为一个整数) 并以 lua_Integer 类型返回这个整数值。

luaL_checknumber : luaL_checknumber (lua_State *L, int arg); 检查函数的第 arg 个参数是否是一个 数字,并返回这个数字。

luaL_checkstring: luaL_checkstring (lua_State *L, int arg); 检查函数的第 arg 个参数是否是一个 字符串并返回这个字符串。

luaL_checklstring : luaL_checklstring (lua_State *L, int arg, size_t *l); 检查函数的第 arg 个参数是否是一个 字符串,并返回该字符串; 如果 l 不为 NULL , 将字符串的长度填入 *l。字符串内可以是任意二进制数据,包括零字符。

常用取出函数:

luaL_optinteger : luaL_optinteger (lua_State *L , int arg , lua_Integer d);
如果函数的第 arg 个参数是一个 整数(或可以转换为一个整数), 返回该整数。 若该参数不存在或是 nil, 返回 d。 除此之外的情况,抛出错误。

luaL_optnumber : luaL_optnumber (lua_State *L, int arg, lua_Number d);
如果函数的第 arg 个参数是一个 数字,返回该数字。 若该参数不存在或是 nil, 返回 d。 除此之外的情况,抛出错误。

luaL_optstring : luaL_optstring (lua_State *L, int arg, const char *d);
如果函数的第 arg 个参数是一个 字符串,返回该字符串。 若该参数不存在或是 nil, 返回 d。 除此之外的情况,抛出错误。

luaL_optlstring :luaL_optlstring (lua_State *L , int arg , const char *d , size_t *l);
如果函数的第 arg 个参数是一个 字符串,返回该字符串。 若该参数不存在或是 nil, 返回 d。 除此之外的情况,抛出错误。若 l 不为 NULL, 将结果的长度填入 *l 。字符串内可以是任意二进制数据,包括零字符。

常用返回函数:

lua_pushboolean :void lua_pushboolean (lua_State *L, int b); 把 b 作为一个布尔量压栈。

lua_pushinteger : lua_pushinteger (lua_State *L, lua_Integer n); 把值为 n 的整数压栈。

lua_pushnumber : lua_pushnumber (lua_State *L, lua_Number n); 把一个值为 n 的浮点数压栈。

lua_pushstring : lua_pushstring (lua_State *L, const char *s); 将指针 s 指向的零结尾的字符串压栈。 如果 s 为 NULL,将 nil 压栈并返回 NULL。

lua_pushlstring: lua_pushlstring (lua_State *L, const char *s, size_t len); 把指针 s 指向的长度为 len 的字符串压栈。 字符串内可以是任意二进制数据,包括零字符。

注意:

xxxlstring 与 xxxstring 的区别, 前者传二进制数据是必须的!!!

4、第一个函数

(1)创建自定义模块框架

在LuatOS源码目录下找到luat/mouldes文件夹,新建一个名为luat_lib_xxx.c的文件

在c文件中先引入"luat_base.h",这个头文件里面包含了我们需要用到的函数

(2)创建自定义函数

这里先给一个小示例

static int l_jiaFa(lua_State *L)		//函数名一般写l_xxx ,这样可以直观的知道这是一个和lua交互的函数
{									
    int a = luaL_checkinteger(L, 1);	//取出输入的第一个值(int类型)
    int b = luaL_optinteger(L, 2, 0);	//取出输入的第二个值(int类型)
    lua_pushinteger(L, a + b); 			//返回ab之和
    return 1;							//返回一个值
}

分析一下:

函数输入(lua_State *L),前面提到了,C和Lua的调用是在栈里进行的;

a的值是通过luaL_checkinteger(L, 1);获得的,这是一个检查函数,输入的第一个值是int类型的时候,函数会把值返回,在这里就是赋值给了a

b的值是通过luaL_optinteger(L, 2, 0);获得的,只是一个取出函数,取出的对象是lua函数输入的第二个值,如果第二个值不是int类型,则会返回0

lua_pushinteger(L, a + b); 是向lua函数返回值(int类型)

最后的return 1; 代表这个函数的返回值只有一个

(3)注册函数

直接上代码分析

#include "rotable.h"
static const rotable_Reg exa[] =	//exa[]就是你的自定义库名字
    {
        {"jiaFa", l_jiaFa, 0},		//第一个是lua要调用的函数名字,第二个是对应的c函数,第三个是lua_Integer值,一般写0
        {NULL, NULL, 0}};			//结尾要有这一行,代表没有其他的函数了

LUAMOD_API int luaopen_exa(lua_State *L)	//注册一个LUAMOD_API,之后要写入luat_base.h	
{
    luat_newlib(L, exa);			//创建一张新的表,并把列表L中的函数注册进去。
    return 1;
}

这里重点是rotable_Reg

typedef struct rotable_Reg {
 char const* name;
 lua_CFunction func;
 lua_Integer value;
} rotable_Reg;


你需要写几个函数,就在这个结构体里写几个,最后以 {NULL, NULL, 0}结尾,代表结束。

写完上面这一段之后,在luat_base.h里面加上LUAMOD_API int luaopen_exa(lua_State *L);

(4)启用自定义模块

打开芯片/模组 对应的base文件,例如air302的base文件是LuatOS/bsp/air302/src/luat_air302_base.c"

根据上一期的移植教程,我们在loadedlibs[]里加入 {"exa", luaopen_exa}, 代表启用该模块

(5)编译新固件

这里不细说了,在LuatOS仓库里都有编译说明,air302我之前也做过编译教程 链接直达

(6) 编写Lua脚本

-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "exademo"
VERSION = "1.0.0"

-- sys库是标配
_G.sys = require("sys")

local function exatext()
    log.info("jiafa", exa.jiaFa(2,10))
end

sys.timerLoopStart(exatext, 1000)
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!


5、进阶函数

当前面的步骤你已经做完了,确保正确运行后,再往下看进阶部分

这部分我们来讲一下进阶部分

(1)返回字符串

老样子,上代码

static int l_reString(lua_State *L)
{
    if (!lua_isnumber(L, 1))	//如果输入的是字符串(不是number自然就是字符串咯)
    {
        const char *c = luaL_optstring(L, 1, "");
        lua_pushstring(L, c); 	//如果第一个输入值是string,直接返回去
        return 1;
    }
    else
    {
        lua_pushstring(L,"nostring");	//到这里就代表输入的第一个值不是字符串,返回nostring
        return 1;
    }
}

这一部分较基础部分多了判断函数,根据注释了解一下即可,不难的。

(2)多输入多返回

static int l_reMore(lua_State *L)
{
    if (lua_isinteger(L, 1)) 	//判断输入的第一个值是不是int
    {
        lua_pushboolean(L, 1); 	//返回true
        const char *st = luaL_optstring(L, 2, "nostring");
        lua_pushstring(L, st);	//返回字符串
        return 2;
    }
    else
    {
        lua_pushboolean(L, 0); //返回flase
        return 1;
    }
}

这里边看代码边说明;

1、函数首先判断lua输入的第一个值类型,如果是int类型,那么返回第一个bool类型值

2、现在已经确定第一个值是int类型了,那么取出lua输入的第二个值,如果是string那么就赋值给st,如果是nil那就把“nostring”赋值给st

3、返回st,使用函数为lua_pushstring(L, st);

4、分析返回值的定义——我们可以看到在if为真的大括号里,我们先返回了bool类型的值,然后返回了string类型的值,一共返回了两个值,那么return的值我们写为2;在else的大括号里,我们只返回了一个bool类型的值,那么return的值即为1

三、结尾

今天跟大家分享了在LuatOS里面C与Lua的调用,并创建了一个自己的自定义模块,相信大家多多少少都对调用方式有了基础的了解,大家可以在实际应用中多多实践,有问题欢迎提出。

QQ群:

LuatOS大群:1061642968

Luat训练营第一期群:221504157

LuatOS仓库 :https://gitee.com/openLuat/LuatOS

 类似资料: