天天看點

必看!LuatOS自定義C庫全新教程,一文極速上手

必看!LuatOS自定義C庫全新教程,一文極速上手

今天繼續講LuatOS的開發,上一期簡單說了一下如何移植LuatOS,相信很多朋友已經看過了。那麼今天,我就開始講C和Lua調用的部分教程。

LuatOS相關資料及Lua語言的官方定義,詳見以下連結:

LuatOS倉庫:

https://gitee.com/openLuat/LuatOS

Lua 5.3 中文參考手冊:

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

閑談C與Lua的調用

LuatOS自定義C庫可以實作使用者的自定義功能,比如一些對延時要求很高的需求,通過C進行使用會更友善快捷:

Lua使用一個虛拟棧來和C互傳值,也就是說,C和Lua的資料互動是在棧上進行的。

棧的基本概念及原理

棧作為一種資料結構,是一種隻能在一端進行插入和删除操作的特殊線性表。

它按照先進後出的原則存儲資料,先進入的資料被壓入棧底,最後的資料在棧頂,需要讀資料的時候從棧頂開始彈出資料(最後一個資料被第一個讀出來)。

必看!LuatOS自定義C庫全新教程,一文極速上手

入棧就是将一個新的元素放到棧頂,出棧就是從棧頂取出一個元素。棧頂的元素總是最後入棧,需要出棧時,又最先被從棧中取出。

如果你沒接觸過棧,就當它是個先入後出的大袋子,你要做的就是往裡面放東西和拿東西。

無論何時Lua調用C,被調用的函數都得到一個新的棧,這個棧獨立于C函數本身的棧,也獨立于之前的Lua棧。它裡面包含了Lua傳遞給C函數的所有參數,而C函數則把要傳回的結果放入這個棧以傳回給調用者 。

是以,C和Lua的調用說大白話就是——在棧上操作。

常用函數簡介

在寫函數的時候,首先使用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 。

           

常用檢查函數

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的差別,前者傳二進制資料是必須的!!!

第一個函數

建立自定義子產品架構:

在LuatOS源碼目錄下找到luat/mouldes檔案夾,建立一個名為luat_lib_xxx.c的檔案;

在C檔案中先引入"luat_base.h",這個頭檔案裡面包含了我們需要用到的函數。

建立自定義函數:

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; 代表這個函數的傳回值隻有一個。

注冊函數:

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

啟用自定義子產品

打開晶片/模組對應的base檔案,例如air302的base檔案是:

https://gitee.com/openLuat/LuatOS/blob/master/bsp/air302/src/luat_air302_base.c

根據上一期的移植教程,我們在loadedlibs[]裡加入 {"exa", luaopen_exa}, 代表啟用該子產品。

編譯新固件

這裡不細說了,在LuatOS倉庫裡都有編譯說明,air302我之前也做過編譯教程,詳見:

https://doc.openluat.com/article/2047

編寫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()之後後面不要加任何語句!!!!!
           

進階函數

目前面的步驟做完,確定正确運作後,再往下看進階部分。

傳回字元串

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;

 }

}
           

這一部分較基礎部分多了判斷函數,根據注釋了解一下即可,不難的。

多輸入多傳回

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。

上海合宙通信子產品 - 合宙Luat,讓萬物互聯更簡單