前言:
嵌入式開發過程中,我們會使用一些腳本工具輔助我們的工作,例如shel或者python、lua等,今天給大家分享一下,我在工作中用到的lua腳本交互使用。
作者:良知猶存
轉載授權以及圍觀:歡迎關注微信公眾號:羽林君
或者添加作者個人微信:become_me
情節介紹:
工作中, 因為我們的傳感器需要出廠標定,所以我們需要有一個配置文件進行保存我們的傳感器參數,這個文件支持讀取和修改,實現這個功能有很多種方式,常規就是使用一個普通文件進行讀寫。
但是我考慮到,我們數據的復雜性,以及文件注釋的描述,我選擇了xml文件進行數據的保存,但是xml文件操作的庫我又不想去自己寫也不想去外部添加使用,本來就是一個小功能,沒必要再去新增額外鏈接,使用別的xml操作庫,所以我就盯上了我們激光slam建圖算法里面用到的lua腳本,這個lua腳本的包本身以及在內核里面添加並在其他進程使用了,我只需要在我這邊編譯選項加 -llua動態鏈過去就可以多個進程一起使用了。
除了方便,也考慮到lua是一個輕量級的腳本,支持交互調用,比如說我們可以通過代碼內部執行調用lua腳本函數,也可以在lua執行代碼注冊進去的函數。這個比shell和python有很多優勢,shell只能在它腳本生成的終端去執行以及python也是類似,無法進行雙方的函數交互調用。而lua可以交互調用,所以很方便。
lua介紹
Lua ,是巴西里約熱內盧天主教大學里的一個研究小組於 1993 年開發的。是一種輕量小巧的腳本語言,用標准C語言編寫並以源代碼形式開放, 其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。
其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。
Lua 特性
輕量級: 它用標准C語言編寫並以源代碼形式開放,編譯后僅僅一百余K,可以很方便的嵌入別的程序里。
可擴展: Lua提供了非常易於使用的擴展接口和機制:由宿主語言(通常是C或C++)提供這些功能,Lua可以使用它們,就像是本來就內置的功能一樣。
支持面向過程(procedure-oriented)編程和函數式編程(functional programming);
自動內存管理;只提供了一種通用類型的表(table),用它可以實現數組,哈希表,集合,對象;
語言內置模式匹配;閉包(closure);函數也可以看做一個值;提供多線程(協同進程,並非操作系統所支持的線程)支持;
通過閉包和table可以很方便地支持面向對象編程所需要的一些關鍵機制,比如數據抽象,虛函數,繼承和重載等。
Lua 應用場景
游戲開發、獨立應用腳本、Web 應用腳本、擴展和數據庫插件如:MySQL Proxy 和 MySQL WorkBench、安全系統,如入侵檢測系統。
lua交互原理基礎知識
lua和c++是通過一個虛擬棧來交互的。
c++調用lua實際上是:由c++先把數據放入棧中,由lua去棧中取數據,然后返回數據對應的值到棧頂,再由棧頂返回c++。
lua調c++也一樣:先編寫自己的c模塊,然后注冊函數到lua解釋器中,然后由lua去調用這個模塊的函數。
因為在我們設備上本來就有lua庫,所以我開發時候直接就在CMakeLists.txt文件里面增加了 -llua,但是最開始在自己pc驗證的時候,本機是沒有相應的lua包的,還是下載了官網lua的源碼進行編譯之后,放到我的電腦指定目錄進行操作驗證的。
- lua源碼下載
去官網 http://www.lua.org/download.html 下載
make
make install
編譯好的文件放到了以下三個目錄
- /usr/local/bin 解釋器目錄
- /usr/local/include 頭文件目錄
- /usr/local/lib 動態鏈接庫目錄
在后面我們進行本機測試代碼時候,就可以加上絕對目錄,進行頭文件搜索和動態庫鏈接了。
下面是我的一個demo測試的Makefile
文件內容,其中就用了頭文件目錄
和動態鏈接庫目錄
。
OBJS = test_cpp_lua.o
CFLAGS = -Wall -g -std=c++11
CC = gcc
CPP = g++
INCLUDES +=-I /usr/local/include
LIBS += -L /usr/local/lib -llua -ldl
#LIBS = -ldl -llua
target:${OBJS}
# g++ -o target test_cpp_lua.o -llua -ldl
@echo "-- start " ${CC} ${CFLAGS} ${OBJS} -o $@ ${INCLUDES} ${LIBS}
$(CPP) ${CFLAGS} ${OBJS} -o $@ ${INCLUDES} ${LIBS}
clean:
-rm -f *.o core *.core target
.cpp.o:
#%.o:%.cpp
${CPP} ${CFLAGS} ${INCLUDES} -c $<
注意:我在編譯時候還用了-ldl ,是因為程序中使用dlopen
、dlsym
、dlclose
、dlerror
顯示加載動態庫,需要設置鏈接選項 -ldl
加載動態鏈接庫,首先為共享庫分配物理內存,然后在進程對應的頁表項中建立虛擬頁和物理頁面之間的映射。
Lua是一種嵌入式腳本語言,即Lua不是可以單獨運行的程序,在實際應用中,主要存在兩種應用形式。第一種形式是,C/C++作為主程序,調用Lua代碼,此時可以將Lua看做“可擴展的語言”,我們將這種應用稱為“應用程序代碼”。第二種形式是Lua具有控制權,而C/C++代碼則作為Lua的“庫代碼”。在這兩種形式中,都是通過Lua提供的C API完成兩種語言之間的通信的。
接下來我給大家分別介紹兩者調用的用法,以及補充到我自己實際使用xml文件的讀寫的操作demo。本文沒有過多描述lua腳本語言的使用操作,僅僅做一些實際調用過程中的應用分享。
C/C++代碼調用 lua變量和函數
首先我們最常用的就是進行腳本的調用,來個最常見的調用機制,在代碼里面執行調用腳本里面函數或者獲得腳本文件里面的一些設置信息。
這lua腳本里面的代碼部分:
debug_enbale = "enable"
angle_table = {
roll_offset = 0.05 ,
pitch_offset = 0.0,
yaw_offset = 0.0,
}
for i,v in ipairs(angle_table) do
print(i,v)
end
這個里面定義了一個字符串變量 debug_enbale
,和一個 lua的table angle_table
,最后還有一個進行table遍歷的for循環流程控制代碼。這樣在執行lua腳本時候就可以打印對應table里面變量信息
在lua中,lua堆棧就是一個struct,堆棧索引的方式可是是正數也可以是負數,區別是:正數索引1永遠表示棧底,負數索引-1永遠表示棧頂。所以我們在使用過程中會看到push_x 和 to_x這樣的函數,就是進行堆棧的操作。
這部分是常規的使用,我在里面分別獲取了number數據和string數據,放到我的執行代碼的運行變量中去。
#include "lua.hpp"
#include <iostream>
int main(int argc,char ** argv)
{
lua_State *pLua = luaL_newstate();
if(!pLua)
{
LOG(Info, "Failed to open Lua!");
return false;
}
luaL_openlibs(pLua);
int bRet = luaL_loadfile(pLua, lua_path.c_str());
if (bRet)
{
LOG(Info, "load .lua file failed" );
return false;
}
// 執行lua文件
bRet = lua_pcall(pLua, 0, 0, 0);
if (bRet)
{
LOG(Info, "call .lua file failed" );
return false;
}
lua_getglobal(pLua, "debug_enbale");
std::string str = lua_tostring(pLua, -1);//獲得lua腳本debug_enbale位置的數據
LOG(Info, "debug_enbale=" << str);
auto get_float_data_from_lua = [&](const char * table_name,const char * value_name) -> float{
lua_getglobal(pLua, table_name);
lua_getfield(pLua, -1, value_name);
return lua_tonumber(pLua, -1);
};
roll_offset = get_float_data_from_lua("angle_table","roll_offset");
pitch_offset = get_float_data_from_lua("angle_table","pitch_offset");
yaw_offset = get_float_data_from_lua("angle_table","yaw_offset");
LOG(Info, "angle_table:"
<< roll_offset <<" "
<< pitch_offset <<" "
<< yaw_offset );
lua_close(pLua);
}
重要函數描述
1.因為工程是cpp,所以添加lua.hpp,如果是C工程,可以直接包含lua.h。
2.lua_State *pLua = luaL_newstate(); Lua庫中沒有定義任何全局變量,而是將所有的狀態都保存在動態結構lua_State中,后面所有的C API都需要該指針作為第一個參數。
3.luaL_openlibs函數是用於打開Lua中的所有標准庫,如io庫、string庫等。4.luaL_loadfile實際調用了lua_load函數來加載lua文件。
5.lua_pcall函數會將程序塊從棧中彈出,並在保護模式下運行該程序塊。執行成功返回0,否則將錯誤信息壓入棧中。
6.lua_getglobal調用這個宏的時候,都會將Lua代碼中與之相應的全局變量值壓入棧中7.lua_tostring函數中的-1,表示棧頂的索引值,棧底的索引值為1,以此類推。該函數將返回棧頂的字符串信息
7.lua_getfield把堆棧中指定索引-1為棧頂 angle_table中的value_name的具體值push到堆棧。
8.lua_tonumber 把棧頂中數據以數值形式返回
9.lua_close用於釋放狀態指針所引用的資源。
其中,使用有數據區分的函數lua_tonumber和lua_tostring兩種函數,lua_tonumber返回包括整形和浮點型。
這樣我就獲得了lua腳本里面我寫好的數據,用來配合我代碼執行。
lua變量 調用C/C++代碼函數
引用文章《Step By Step(Lua調用C函數)》
Lua可以調用C函數的能力將極大的提高Lua的可擴展性和可用性。對於有些和操作系統相關的功能,或者是對效率要求較高的模塊,我們完全可以通過C函數來實現,之后再通過Lua調用指定的C函數。對於那些可被Lua調用的C函數而言,其接口必須遵循Lua要求的形式,即typedef int (lua_CFunction)(lua_State L)。
簡單說明一下,該函數類型僅僅包含一個表示Lua環境的指針作為其唯一的參數,實現者可以通過該指針進一步獲取Lua代碼中實際傳入的參數。返回值是整型,表示該C函數將返回給Lua代碼的返回值數量,如果沒有返回值,則return 0即可。需要說明的是,C函數無法直接將真正的返回值返回給Lua代碼,而是通過虛擬棧來傳遞Lua代碼和C函數之間的調用參數和返回值的。這里我們將介紹兩種Lua調用C函數的規則。
Lua調用C函數有兩種方式
1、程序主體在C中運行,C函數注冊到Lua中。C調用Lua,Lua調用C注冊的函數,C得到函數的執行結果。
2、程序主體在Lua中運行,C函數作為庫函數供Lua使用。
第一種方式看起來很羅嗦,也很奇怪。既然程序主體運行在C中,而且最終使用的也是C中定義的函數,那么為何要將函數注冊給Lua,然后再通過Lua調用函數呢?
所以相比於第一種方式,第二種方式使用的更加普遍。
關於這部分代碼我也只是在我電腦上跑了幾個范例,大家也可以去網上自己去查找相關例子,我自己在設備上並沒有使用到這個部分,后續我有使用可以再寫這部分應用給大家分享。
lua進行xml文件的操作
這個部分是因為之前的功能做了一些修正,原因是我們需要的傳感器參數放置的文件可以被修改,如果使用lua腳本里面寫入參數,那么參數相當與定死了,我們后續是無法使用lua腳本修改里面本身的數據的,所以后來我就使用xml文件放置我的傳感器參數,使用lua腳本進行讀寫。
xml是一個常用來寫一些我們不定數據配置文件的格式,lua也有luaxml,工具包,但是我為了不新增額外庫實現,所以使用了lua里面I/O庫(lua用於讀取和處理文件的庫)讀寫的方法進行讀寫xml文件。
大家也可以用xml一個lua其他工具進行使用,會更加方便,下面分享一個官方給的鏈接:
http://lua-users.org/wiki/LuaXml
里面分為四種不同方法的xml讀寫工具:
- 工具包;
- 僅限 Lua 的 XML 解析器;
- 包含 C 代碼和綁定的 XML 解析器;
- 用於處理基於 XML 的協議(例如 XML-RPC 和 SOAP)的模塊。
lua腳本中讀寫xml函數
function get_value_from_xml(path,element_name)
xml_file=path
element=element_name
head="<"..element..">"
tail="</"..element..">"
file = io.open(xml_file, "r"); --打開xml文件
data = file:read("*all"); --讀取文件的全部內容到data變量中
file:close(); --關閉xml文件
--獲取起始tag與關閉tag之間的內容到value中
_,_,value=string.find(data, head.."(.-)"..tail)
--輸出value的值到標准輸出
-- print(value)
return value
end
function set_value_to_xml(path,element_name,set_value)
xml_file=path
element=element_name
new_value=set_value
head="<"..element..">" --根據元素名生成起始tag,即<element_name>
tail="</"..element..">" --根據元素名生成關閉tag,即</element_name>
file = io.open(xml_file, "r"); --打開xml文件
data = file:read("*all"); --讀取文件的全部內容到data變量中
file:close(); --關閉xml文件
--將element之前的內容,element的值,element之后的內容,分別保存在pre,old_value,follow中
_,_,pre,old_value,follow=string.find(data, "(.*)("..head..".-"..tail..")(.*)")
file = io.open(xml_file, "w"); --打開xml文件
file:write(pre..head..new_value..tail..follow); --拼裝出新的文件內容,並寫入
file:close(); --關閉xml文件
end
上面主要是利用了 string.find 它的三個參數和三個返回值。string.find 功能是從字符串中找到特定的內容。
第一個參數是目標字符串(所有的內容,比如data),第二個參數是想要找的字符串(比如 item 之間的內容),第三個參數是從第幾個字符開始找起(我讓它成為一個不斷變化的值)。
第一個返回值是找到的字符串( item 之間的內容)的首字符位置(一個數字),第二個返回值是找到的字符串( item 之間的內容)的尾字符位置(一個數字),第三個是找到的字符串( item 之間的內容)。
所以代碼就是靠每次循環的第三個返回值來獲取內容。
cpp代碼:
#include "lua.hpp"
#include <iostream>
int main(int argc,char ** argv)
{
lua_State *pLua = luaL_newstate();
if(!pLua)
{
LOG(Info, "Failed to open Lua!");
return false;
}
luaL_openlibs(pLua);
int bRet = luaL_loadfile(pLua, lua_path.c_str());
if (bRet)
{
LOG(Info, "load .lua file failed" );
return false;
}
// 執行lua文件
bRet = lua_pcall(pLua, 0, 0, 0);
if (bRet)
{
LOG(Info, "call .lua file failed" );
return false;
}
auto lua_func_call_wirte = [&](const char * func_name, const char * key_name,float &value){
lua_getglobal(pLua, func_name);
lua_pushstring(pLua ,surface_xml_used_path.c_str());
lua_pushstring(pLua ,key_name);
lua_pushnumber(pLua ,value);
bRet = lua_pcall(pLua, 3, 1, 0);//三個參數 一個返回值
if (bRet)
{
const char* pErrorMsg = lua_tostring(pLua, -1);
LOG(Info, "lua_pcall - ErrorMsg:" << pErrorMsg );
// lua_close(pLua);
return false;
}
if (lua_isnumber(pLua, -1))
{
value = lua_tonumber(pLua, -1);
LOG(Info, "surface_config " << value);
return true;
}
return false;
};
float roll_offset = 0.5,pitch_offset = 0.2,yaw_offset=0.6;
lua_func_call_wirte("set_value_to_xml"," roll_offset", roll_offset);
lua_func_call_wirte("set_value_to_xml"," pitch_offset", pitch_offset);
lua_func_call_wirte("set_value_to_xml"," yaw_offset", yaw_offset);
auto lua_func_call_number = [&](const char * func_name, const char * key_name,float &value){
lua_getglobal(pLua, func_name);
lua_pushstring(pLua ,surface_xml_used_path.c_str());
lua_pushstring(pLua ,key_name);
bRet = lua_pcall(pLua, 2, 1, 0);//兩個參數 一個返回值
if (bRet)
{
const char* pErrorMsg = lua_tostring(pLua, -1);
LOG(Info, "lua_pcall - ErrorMsg:" << pErrorMsg );
// lua_close(pLua);
return false;
}
if (lua_isnumber(pLua, -1))
{
value = lua_tonumber(pLua, -1);
LOG(Info, "config " << value);
return true;
}
return false;
};
auto lua_func_call_string = [&](const char * func_name, const char * key_name,std::string &value){
lua_getglobal(pLua, func_name);
lua_pushstring(pLua ,surface_xml_used_path.c_str());
lua_pushstring(pLua ,key_name);
bRet = lua_pcall(pLua, 2, 1, 0);
if (bRet)
{
const char* pErrorMsg = lua_tostring(pLua, -1);
ZY_LOG("robotctl", kInfo, "lua_pcall - ErrorMsg:" << pErrorMsg );
lua_close(pLua);
return false;
}
if (lua_isstring(pLua, -1))
{
value = lua_tostring(pLua, -1);
LOG(Info, "config " << value.c_str() );
return true;
}
return false;
};
std::string temp_from_lua;
lua_func_call_string("get_value_from_xml","debug_enable",temp_from_lua);
LOG(Info, "debug_enbale =" << temp_from_lua);
lua_func_call_number("get_value_from_xml","down_roll_offset",roll_offset);
lua_func_call_number("get_value_from_xml","down_pitch_offset",pitch_offset);
lua_func_call_number("get_value_from_xml","down_yaw_offset",yaw_offset);
LOG(Info, "ngle_table:"
<< roll_offset <<" "
<< pitch_offset <<" "
<< yaw_offset );
lua_close(pLua);
}
xml文件內容:
<debug_enable>enable</debug_enable>
<roll_offset>0.0</roll_offset>
<pitch_offset>0.0</pitch_offset>
<yaw_offset>0.0</yaw_offset>
看到這塊大家可能會問我,為什么不直接使用linux下直接做一個文件進行讀寫配置數據呢,我的想法是,純文件不好進行注釋,因為我的配置參數有些很長,我想把它盡可能讓別人看懂,所以我寫了一些注釋進去,xml很符合我的要求,此外lua腳本也一些腳本使用過程中,很好的可以輔助我本身的代碼執行,所以我就考慮把一些整體差不多執行的功能操作可以集成到一起,統一接口去執行。所以最后選擇了lua+xml,技術有很多種實現思路,但是我們需要衡量一下哪些部分的技術可以讓平台可以重復利用的多一些。
結語
這就是我分享我在工作中使用lua腳本的操作,如果大家有更好的想法和需求,也歡迎大家加我好友交流分享哈。
作者:良知猶存,白天努力工作,晚上原創公號號主。公眾號內容除了技術還有些人生感悟,一個認真輸出內容的職場老司機,也是一個技術之外豐富生活的人,攝影、音樂 and 籃球。關注我,與我一起同行。
‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧
推薦閱讀
【3】CPU中的程序是怎么運行起來的 必讀
本公眾號全部原創干貨已整理成一個目錄,回復[ 資源 ]即可獲得。