
今天繼續講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的資料互動是在棧上進行的。
棧的基本概念及原理
棧作為一種資料結構,是一種隻能在一端進行插入和删除操作的特殊線性表。
它按照先進後出的原則存儲資料,先進入的資料被壓入棧底,最後的資料在棧頂,需要讀資料的時候從棧頂開始彈出資料(最後一個資料被第一個讀出來)。
入棧就是将一個新的元素放到棧頂,出棧就是從棧頂取出一個元素。棧頂的元素總是最後入棧,需要出棧時,又最先被從棧中取出。
如果你沒接觸過棧,就當它是個先入後出的大袋子,你要做的就是往裡面放東西和拿東西。
無論何時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,讓萬物互聯更簡單