C++和Lua交互教程(基于LuaBridge)
作者:查志旺 ,向日葵遠程控制軟件前端開發工程師。
最近公司需要做向日葵遠程控制軟件跨平臺項目,為了代碼的可復用性,需嵌入跨平臺腳本語言,我們選擇了Lua,理由是Lua由標準C編寫而成,幾乎在所有操作系統和平臺上都可以編譯,Lua腳本可以很容易的被C/C++ 代碼調用,也可以反過來調用C/C++的函數,今天就跟大家分享下c++與Lua交互的一些問題。
為了方便c++和lua的交互,我引進了LuaBridge。因為它源碼簡單易用,只有頭文件,沒有.cpp文件,不需要編譯,只需要引用頭文件即可,方便快捷。
下面我用vs2008工具寫了一個win32控制臺項目例子來講解下c++和Lua簡單的互調。
一、準備工作
從lua官網上下載最新的http://www.lua.org/download.html,下個最新的lua-5.3.4.tar.gz解壓出來后可以直接拿源碼編譯成lib或者直接加入到項目中.我是直接把它放到項目中,編譯的時候,會出現多個main函數入口的錯誤(移除對應的文件)。
下載LuaBridge,下載地址:https://github.com/vinniefalco/LuaBridge列表下的Source/LuaBridge是源碼,Manual.html是幫助文檔。
創建win32控制臺項目test_lua把lua源碼和LuaBridge頭文件放入到項目目錄下并添加到項目工程中。
二、編寫代碼
1、先引用Lua和LuaBridge頭文件如下:
//引用c文件的頭文件所以需要加上extern "C" extern "C" {#include "lua.h"#include "lauxlib.h"#include "lualib.h" } #include "LuaBridge\LuaBridge.h"2、然后創建test_lua類和一個子類代碼如下:
class test_lua { public:test_lua(){m_test_string = "c++ test string";}~test_lua(){} //test方法void test(int a,int b){printf("c++ test function %d+%d=%d\n", a, b, a+b);}//屬性set方法void SetName(std::string name){m_name = name;} //屬性get方法,注意需要后面加conststd::string GetName() const{return m_name;} //供lua調用方法,返回多個參數方法int cFunc(lua_State* L){ //返回參數1lua_pushstring(L,"str1"); //返回參數1lua_pushstring(L,"str2"); //返回參數個數return 2;}std::string m_test_string;std::string m_name;static int m_static_data; }; //test_lua靜態變量定義(靜態變量在類內只是聲明) int test_lua::m_static_data; //test_lua子類 class test_lua_child :public test_lua {public:test_lua_child(std::string test):m_test_child_string(test){printf("call test_lua_child constructor\n");}~test_lua_child(){}std::string m_test_child_string; };3、創建一個lua腳本文件a.lua內容為:
--lua 打印lua script print("lua script") --調用成員變量m_test_string(test_str為注冊的名字) print(test_lua.test_str) --調用c++靜態變量(需要加上test命名空間) test.test_lua.static_data=12 print("static_data: "..test.test_lua.static_data) --調用c++類test_lua屬性name test_lua.name="name_property"; print("name: "..test_lua.name); --lua調用c++方法test_lua為c++類在lua的注冊名,調用test方法 test_lua:test(3,4)--調用c++調用方法返回多個值 local ret1,ret2 = test_lua:cFunc() print("ret1="..ret1.." ret2="..ret2)--創建test_lua_child對象 local test_lua_child = test.test_lua_child("test_string") --調用其變量 print("child string:"..test_lua_child.test_child_string); --調用父類的name屬性 test_lua_child.name="child_name_property"; print("name:"..test_lua_child.name);--lua 方法加法 function lua_add_function(a,b)print("lua_add_function") return a+b; end--lua 方法字符串加法(..是相加語法) function lua_add_str_function(a,b)print("lua_add_str_function") return a..b; end4、主函數編寫
4.1、Lua的初始化和加載Lua的基本庫
//初始化Lua (最后記得調用lua_close(lua_state)釋放)lua_State* lua_state = luaL_newstate(); //加載Lua基本庫luaL_openlibs(lua_state);4.2、用luabridge注冊到lua中
在這里要注意的是多個類注冊需要加一個namespace(test),且.endClass()后面不加分號
luabridge::getGlobalNamespace(lua_state).beginNamespace("test").beginClass<test_lua>("test_lua").addConstructor<void (*) (void)> ()//無參構造函數的注冊.addData("test_str",&test_lua::m_test_string)//注冊變量到lua.addStaticData("static_data", &test_lua::m_static_data)//注冊靜態變量到lua.addFunction("test", &test_lua::test)//注冊test、方法到lua(addStaticFunction靜態函數注冊也類似).addProperty("name",&test_lua::GetName,&test_lua::SetName)//屬性方法的注冊(addStaticProperty靜態屬性方法也類似).addCFunction("cFunc",&test_lua::cFunc)//注冊返回多個參數給lua的方法.endClass().deriveClass<test_lua_child, test_lua> ("test_lua_child")//子類的注冊.addConstructor<void (*) (std::string)> ()//有參構造函數的注冊.addData("test_child_string", &test_lua_child::m_test_child_string)//注冊變量到lua.endClass().endNamespace();//創建test_lua對象 test_lua test;luabridge::setGlobal(lua_state, &test, "test_lua");//注冊test_lua對象到lua注:test_lua也可以在lua創建,因為構造函數也注冊到lua,如test.test_lua(),上面的a.lua腳本有子類test_lua_child的創建和調用其父類的屬性方法
4.3、注冊完成后,再返回看上面寫的a.lua腳本就知道每個調用的意義,添加運行Lua腳本 代碼然后執行,代碼如下:
//運行lua腳本luaL_dofile(lua_state, "a.lua"); //關閉Lua lua_close(lua_state); 編譯執行結果為: lua script c++ test string static_data: 12 name: name_property c++ test function 3+4=7 ret1=str1 ret2=str2 call test_lua_child constructor child string:test_string name:child_name_property4.4、c++調用lua方法,因為lua方法函數參數一樣而且都是一個返回值,為了方便,采用模板形式(以兩個參數為例)第一個參數(lua對象)和第二個參數(方法名)類型固定,后面參數用模板
template<typename R, typename T1, typename T2> R call(lua_State* lua_state,const char* name, T1 arg1, T2 arg2) { //讀取方法名lua_getglobal(lua_state, name); //判斷是不是方法if (lua_isfunction(lua_state, -1)){ //壓入參數 luabridge::Stack<T1>::push(lua_state, arg1); luabridge::Stack<T2>::push(lua_state, arg2); //執行函數(參數為lua對象、參數個數,返回值個數,出錯返回)lua_pcall(lua_state, 2, 1, 0);} //獲取返回值 return luabridge::Stack<R>::get(lua_state, -1); } 在運行lua腳本后面再加上如下調用代碼: //調用lua方法lua_add_functionint ret = call<int>(lua_state,"lua_add_function", 5, 6);//調用lua方法lua_add_str_function std::string value = call<const char*>(lua_state,"lua_add_str_function","5", "6");printf("lua_add_function result:%d\n", ret);printf("lua_add_str_function result:%s\n", value.c_str());編譯執行結果為:
lua script c++ test string static_data: 12 name: name_property c++ test function 3+4=7 ret1=str1 ret2=str2 call test_lua_child constructor child string:test_string name:child_name_property lua_add_function lua_add_str_function lua_add_function result:11 lua_add_str_function result:564.5、最后講一下luaL_dostring
luaL_dostring跟luaL_dofile是一個作用,都是加載并運行lua腳本,只是對象不一樣,看方法名就知道是一個是加載文件,另外一個是加載string,最后運行里面的lua腳本,luaL_dostring在lua嵌入到其他的腳本語言中經常用到,現在沿用上面的例子在lua_close之前加段代碼簡單說明下:
//定義lua腳本,調用test_lua類里的屬性name并打印出來 std::string lua_string = "print(\"run lua string test_lua name:\"..test_lua.name)"; //加載string并運行 luaL_dostring(lua_state, lua_string.c_str()); 編譯運行得到的結果為: run lua string test_lua name:name_property說到嵌入問題,現在做的向日葵遠程控制軟件的界面用的是xml,這里就涉及到lua嵌入到xml中,由于lua特性,在其他的系統這些xml都可以用,所以以后再也不用擔心加個新界面每個系統還得重新再搞一套,lua嵌入xml中原理就是把lua腳本加入到一個節點中如:
<script><![CDATA[ --lua代碼 print("run lua script")]]></script>解析xml對應的script節點內容,然后用luaL_dostring去加載運行就可以了。
最后配上現在的向日葵界面圖
向日葵客戶端:
向日葵控制端:
三、小結
希望通過上面的簡單例子可以幫助大家快速上手c++嵌入lua腳本,從上面代碼也可以看出lua和c++很容易互相調用,lua與c++是通過操作虛擬棧來交互的,例如上面調用lua方法,就是c++先把方法放入到棧頂,然后lua從棧頂取值操作,然后把結果又放回到棧頂,c++再從棧頂取值,lua調用c++也類似,就是c++把需要調用的先注冊到lua中,lua就可以調用,想了解更多lua基本語法和原理的可以具體查看lua中的manual.html,LuaBridge的其它用法也可查看LuaBridge的manual.html。
總結
以上是生活随笔為你收集整理的C++和Lua交互教程(基于LuaBridge)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 没u盘怎么pe启动 如何在没有U盘的情况
- 下一篇: 电脑下载win7系统怎么安装 电脑安装w