• 正文
    • lua介紹
    • lua交互原理基礎知識
    • C/C++代碼調(diào)用 lua變量和函數(shù)
    • lua變量 調(diào)用C/C++代碼函數(shù)
    • lua進行xml文件的操作
    • 結(jié)語
  • 相關推薦
申請入駐 產(chǎn)業(yè)圖譜

c、c++和lua的交互使用分享

02/13 09:16
230
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

嵌入式開發(fā)過程中,我們會使用一些腳本工具輔助我們的工作,例如shel或者python、lua等,今天給大家分享一下,我在工作中用到的lua腳本交互使用。

歡迎關注微信公眾號:羽林君,或者添加作者個人微信:become_me

情節(jié)介紹:

工作中, 因為我們的傳感器需要出廠標定,所以我們需要有一個配置文件進行保存我們的傳感器參數(shù),這個文件支持讀取和修改,實現(xiàn)這個功能有很多種方式,常規(guī)就是使用一個普通文件進行讀寫。

但是我考慮到,我們數(shù)據(jù)的復雜性,以及文件注釋的描述,我選擇了xml文件進行數(shù)據(jù)的保存,但是xml文件操作的庫我又不想去自己寫也不想去外部添加使用,本來就是一個小功能,沒必要再去新增額外鏈接,使用別的xml操作庫,所以我就盯上了我們激光slam建圖算法里面用到的lua腳本,這個lua腳本的包本身以及在內(nèi)核里面添加并在其他進程使用了,我只需要在我這邊編譯選項加 -llua動態(tài)鏈過去就可以多個進程一起使用了。

除了方便,也考慮到lua是一個輕量級的腳本,支持交互調(diào)用,比如說我們可以通過代碼內(nèi)部執(zhí)行調(diào)用lua腳本函數(shù),也可以在lua執(zhí)行代碼注冊進去的函數(shù)。這個比shell和python有很多優(yōu)勢,shell只能在它腳本生成的終端去執(zhí)行以及python也是類似,無法進行雙方的函數(shù)交互調(diào)用。而lua可以交互調(diào)用,所以很方便。

lua介紹

Lua ,是巴西里約熱內(nèi)盧天主教大學里的一個研究小組于 1993 年開發(fā)的。是一種輕量小巧的腳本語言,用標準C語言編寫并以源代碼形式開放, 其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。

Lua 特性

輕量級: 它用標準C語言編寫并以源代碼形式開放,編譯后僅僅一百余K,可以很方便的嵌入別的程序里??蓴U展: Lua提供了非常易于使用的擴展接口和機制:由宿主語言(通常是C或C++)提供這些功能,Lua可以使用它們,就像是本來就內(nèi)置的功能一樣。

支持面向過程(procedure-oriented)編程和函數(shù)式編程(functional programming);自動內(nèi)存管理;只提供了一種通用類型的表(table),用它可以實現(xiàn)數(shù)組,哈希表,集合,對象;語言內(nèi)置模式匹配;閉包(closure);函數(shù)也可以看做一個值;提供多線程(協(xié)同進程,并非操作系統(tǒng)所支持的線程)支持;通過閉包和table可以很方便地支持面向?qū)ο缶幊趟枰囊恍╆P鍵機制,比如數(shù)據(jù)抽象,虛函數(shù),繼承和重載等。

Lua 應用場景

游戲開發(fā)、獨立應用腳本、Web 應用腳本、擴展和數(shù)據(jù)庫插件如:MySQL Proxy 和 MySQL WorkBench、安全系統(tǒng),如入侵檢測系統(tǒng)。

lua交互原理基礎知識

lua和c++是通過一個虛擬棧來交互的。c++調(diào)用lua實際上是:由c++先把數(shù)據(jù)放入棧中,由lua去棧中取數(shù)據(jù),然后返回數(shù)據(jù)對應的值到棧頂,再由棧頂返回c++。lua調(diào)c++也一樣:先編寫自己的c模塊,然后注冊函數(shù)到lua解釋器中,然后由lua去調(diào)用這個模塊的函數(shù)。

因為在我們設備上本來就有l(wèi)ua庫,所以我開發(fā)時候直接就在CMakeLists.txt文件里面增加了 -llua,但是最開始在自己pc驗證的時候,本機是沒有相應的lua包的,還是下載了官網(wǎng)lua的源碼進行編譯之后,放到我的電腦指定目錄進行操作驗證的。

    lua源碼下載

去官網(wǎng) http://www.lua.org/download.html ?下載

make

make install

編譯好的文件放到了以下三個目錄

    /usr/local/bin ?解釋器目錄/usr/local/include 頭文件目錄/usr/local/lib 動態(tài)鏈接庫目錄

在后面我們進行本機測試代碼時候,就可以加上絕對目錄,進行頭文件搜索和動態(tài)庫鏈接了。

下面是我的一個demo測試的Makefile文件內(nèi)容,其中就用了頭文件目錄動態(tài)鏈接庫目錄。

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、dlsymdlclose、dlerror 顯示加載動態(tài)庫,需要設置鏈接選項 -ldl

加載動態(tài)鏈接庫,首先為共享庫分配物理內(nèi)存,然后在進程對應的頁表項中建立虛擬頁和物理頁面之間的映射。

Lua是一種嵌入式腳本語言,即Lua不是可以單獨運行的程序,在實際應用中,主要存在兩種應用形式。第一種形式是,C/C++作為主程序,調(diào)用Lua代碼,此時可以將Lua看做“可擴展的語言”,我們將這種應用稱為“應用程序代碼”。第二種形式是Lua具有控制權(quán),而C/C++代碼則作為Lua的“庫代碼”。在這兩種形式中,都是通過Lua提供的C API完成兩種語言之間的通信的。

接下來我給大家分別介紹兩者調(diào)用的用法,以及補充到我自己實際使用xml文件的讀寫的操作demo。本文沒有過多描述lua腳本語言的使用操作,僅僅做一些實際調(diào)用過程中的應用分享。

C/C++代碼調(diào)用 lua變量和函數(shù)

首先我們最常用的就是進行腳本的調(diào)用,來個最常見的調(diào)用機制,在代碼里面執(zhí)行調(diào)用腳本里面函數(shù)或者獲得腳本文件里面的一些設置信息。

這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循環(huán)流程控制代碼。這樣在執(zhí)行l(wèi)ua腳本時候就可以打印對應table里面變量信息

在lua中,lua堆棧就是一個struct,堆棧索引的方式可是是正數(shù)也可以是負數(shù),區(qū)別是:正數(shù)索引1永遠表示棧底,負數(shù)索引-1永遠表示棧頂。所以我們在使用過程中會看到push_x 和 to_x這樣的函數(shù),就是進行堆棧的操作。

這部分是常規(guī)的使用,我在里面分別獲取了number數(shù)據(jù)和string數(shù)據(jù),放到我的執(zhí)行代碼的運行變量中去。

#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;
????}
???//?執(zhí)行l(wèi)ua文件
????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位置的數(shù)據(jù)
????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);
????????
}

重要函數(shù)描述

1.因為工程是cpp,所以添加lua.hpp,如果是C工程,可以直接包含lua.h。

2.lua_State *pLua = luaL_newstate(); Lua庫中沒有定義任何全局變量,而是將所有的狀態(tài)都保存在動態(tài)結(jié)構(gòu)lua_State中,后面所有的C API都需要該指針作為第一個參數(shù)。3.luaL_openlibs函數(shù)是用于打開Lua中的所有標準庫,如io庫、string庫等。

4.luaL_loadfile實際調(diào)用了lua_load函數(shù)來加載lua文件。

5.lua_pcall函數(shù)會將程序塊從棧中彈出,并在保護模式下運行該程序塊。執(zhí)行成功返回0,否則將錯誤信息壓入棧中。6.lua_getglobal調(diào)用這個宏的時候,都會將Lua代碼中與之相應的全局變量值壓入棧中

7.lua_tostring函數(shù)中的-1,表示棧頂?shù)乃饕?,棧底的索引值?,以此類推。該函數(shù)將返回棧頂?shù)淖址畔?/p>

7.lua_getfield把堆棧中指定索引-1為棧頂 angle_table中的value_name的具體值push到堆棧。

8.lua_tonumber 把棧頂中數(shù)據(jù)以數(shù)值形式返回

9.lua_close用于釋放狀態(tài)指針所引用的資源。

其中,使用有數(shù)據(jù)區(qū)分的函數(shù)lua_tonumber和lua_tostring兩種函數(shù),lua_tonumber返回包括整形和浮點型。

這樣我就獲得了lua腳本里面我寫好的數(shù)據(jù),用來配合我代碼執(zhí)行。

lua變量 調(diào)用C/C++代碼函數(shù)

引用文章《Step By Step(Lua調(diào)用C函數(shù))》
Lua可以調(diào)用C函數(shù)的能力將極大的提高Lua的可擴展性和可用性。對于有些和操作系統(tǒng)相關的功能,或者是對效率要求較高的模塊,我們完全可以通過C函數(shù)來實現(xiàn),之后再通過Lua調(diào)用指定的C函數(shù)。對于那些可被Lua調(diào)用的C函數(shù)而言,其接口必須遵循Lua要求的形式,即typedef int (lua_CFunction)(lua_State L)。簡單說明一下,該函數(shù)類型僅僅包含一個表示Lua環(huán)境的指針作為其唯一的參數(shù),實現(xiàn)者可以通過該指針進一步獲取Lua代碼中實際傳入的參數(shù)。返回值是整型,表示該C函數(shù)將返回給Lua代碼的返回值數(shù)量,如果沒有返回值,則return 0即可。需要說明的是,C函數(shù)無法直接將真正的返回值返回給Lua代碼,而是通過虛擬棧來傳遞Lua代碼和C函數(shù)之間的調(diào)用參數(shù)和返回值的。這里我們將介紹兩種Lua調(diào)用C函數(shù)的規(guī)則。

Lua調(diào)用C函數(shù)有兩種方式

1、程序主體在C中運行,C函數(shù)注冊到Lua中。C調(diào)用Lua,Lua調(diào)用C注冊的函數(shù),C得到函數(shù)的執(zhí)行結(jié)果。

2、程序主體在Lua中運行,C函數(shù)作為庫函數(shù)供Lua使用。第一種方式看起來很羅嗦,也很奇怪。既然程序主體運行在C中,而且最終使用的也是C中定義的函數(shù),那么為何要將函數(shù)注冊給Lua,然后再通過Lua調(diào)用函數(shù)呢?

所以相比于第一種方式,第二種方式使用的更加普遍。

關于這部分代碼我也只是在我電腦上跑了幾個范例,大家也可以去網(wǎng)上自己去查找相關例子,我自己在設備上并沒有使用到這個部分,后續(xù)我有使用可以再寫這部分應用給大家分享。

lua進行xml文件的操作

這個部分是因為之前的功能做了一些修正,原因是我們需要的傳感器參數(shù)放置的文件可以被修改,如果使用lua腳本里面寫入?yún)?shù),那么參數(shù)相當與定死了,我們后續(xù)是無法使用lua腳本修改里面本身的數(shù)據(jù)的,所以后來我就使用xml文件放置我的傳感器參數(shù),使用lua腳本進行讀寫。

xml是一個常用來寫一些我們不定數(shù)據(jù)配置文件的格式,lua也有l(wèi)uaxml,工具包,但是我為了不新增額外庫實現(xiàn),所以使用了lua里面I/O庫(lua用于讀取和處理文件的庫)讀寫的方法進行讀寫xml文件。

大家也可以用xml一個lua其他工具進行使用,會更加方便,下面分享一個官方給的鏈接:http://lua-users.org/wiki/LuaXml

里面分為四種不同方法的xml讀寫工具:

    工具包;僅限 Lua 的 XML 解析器;包含 C 代碼和綁定的 XML 解析器;用于處理基于 XML 的協(xié)議(例如 XML-RPC 和 SOAP)的模塊。

lua腳本中讀寫xml函數(shù)

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");?????--讀取文件的全部內(nèi)容到data變量中
?file:close();?????????????????--關閉xml文件

?--獲取起始tag與關閉tag之間的內(nèi)容到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..">"????--根據(jù)元素名生成起始tag,即<element_name>
?tail="</"..element..">"???--根據(jù)元素名生成關閉tag,即</element_name>

?file?=?io.open(xml_file,?"r");?--打開xml文件
?data?=?file:read("*all");??????--讀取文件的全部內(nèi)容到data變量中
?file:close();??????????????????--關閉xml文件


?--將element之前的內(nèi)容,element的值,element之后的內(nèi)容,分別保存在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);?--拼裝出新的文件內(nèi)容,并寫入
?file:close();???????????????????????--關閉xml文件
end

上面主要是利用了 string.find 它的三個參數(shù)和三個返回值。string.find 功能是從字符串中找到特定的內(nèi)容。

第一個參數(shù)是目標字符串(所有的內(nèi)容,比如data),第二個參數(shù)是想要找的字符串(比如 item 之間的內(nèi)容),第三個參數(shù)是從第幾個字符開始找起(我讓它成為一個不斷變化的值)。

第一個返回值是找到的字符串( item 之間的內(nèi)容)的首字符位置(一個數(shù)字),第二個返回值是找到的字符串( item 之間的內(nèi)容)的尾字符位置(一個數(shù)字),第三個是找到的字符串( item 之間的內(nèi)容)。

所以代碼就是靠每次循環(huán)的第三個返回值來獲取內(nèi)容。

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;
????}
???//?執(zhí)行l(wèi)ua文件
????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);//三個參數(shù)?一個返回值
????????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);//兩個參數(shù)?一個返回值
????????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);

}

看到這塊大家可能會問我,為什么不直接使用linux下直接做一個文件進行讀寫配置數(shù)據(jù)呢,我的想法是,純文件不好進行注釋,因為我的配置參數(shù)有些很長,我想把它盡可能讓別人看懂,所以我寫了一些注釋進去,xml很符合我的要求,此外lua腳本也一些腳本使用過程中,很好的可以輔助我本身的代碼執(zhí)行,所以我就考慮把一些整體差不多執(zhí)行的功能操作可以集成到一起,統(tǒng)一接口去執(zhí)行。所以最后選擇了lua+xml,技術有很多種實現(xiàn)思路,但是我們需要衡量一下哪些部分的技術可以讓平臺可以重復利用的多一些。

結(jié)語

這就是我分享我在工作中使用lua腳本的操作,如果大家有更好的想法和需求,也歡迎大家加我好友交流分享哈。


作者:良知猶存,白天努力工作,晚上原創(chuàng)公號號主。公眾號內(nèi)容除了技術還有些人生感悟,一個認真輸出內(nèi)容的職場老司機,也是一個技術之外豐富生活的人,攝影、音樂 and 籃球。關注我,與我一起同行。

相關推薦

登錄即可解鎖
  • 海量技術文章
  • 設計資源下載
  • 產(chǎn)業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

一個程序員,喜歡寫文章,還喜歡打籃球,也喜歡吉他鋼琴的駁雜之人。日常更新自己,分享包括但不限于C/C++、嵌入式、物聯(lián)網(wǎng)、Linux等編程學習筆記,同時,公眾號內(nèi)包含大量的學習資源。歡迎關注,一同交流學習,共同進步!