上一篇我們提到許多c的api,這一篇我們就來看看如何實現基本的C++與lua的交互。
(1)基礎示例
首先我們打開VS,新建一個c++控制台程序lua1,在我電腦上,這個新建的c++項目路徑是F:\VSProject\lua1。
然後在lua的安裝目錄下找到include和lib文件夾
將include和lib文件夾拷貝至新建的c++項目中,拷貝到和.sln解決方案文件同一目錄
拷貝完畢後,在vs中右鍵解決方案,找到屬性
在C/C++中的“附加包含目錄”加上../include
在鏈接器中的“附加庫目錄”加上../lib
附加包含目錄和附加庫目錄添加完畢後,就可以在程序中通過#include來加載lua的頭文件了。
lua1.cpp:
// lua1.cpp : 定義控制台應用程序的入口點。 // #include "stdafx.h" #include "iostream" #include "stdio.h" #include "string.h" //lua.h和lualib.h作為C代碼運行,在C++文件中使用lua.h和lualib.h,需使用extern命令實現混合編程,否則會提示無法解析外部函數 extern "C" { #include "lua.h" #include "lauxlib.h" #include "lualib.h" } using namespace std; #pragma comment(lib,"lua5.1.lib") int _tmain(int argc, _TCHAR* argv[]) { char buff[256]; int error; //創建一個lua狀態機(虛擬棧),這個狀態機沒有包含預定義的lua庫函數 lua_State* L = luaL_newstate(); //打開標准庫,這樣狀態機可以使用lua庫函數,可以解釋lua腳本 luaL_openlibs(L); char fileName[] = "F:/LuaFile/lua1.lua"; //加載lua文件,解釋器將lua文件的內容讀取出來,動態編譯為代碼塊 luaL_loadfile(L,fileName); //執行編譯好的代碼塊,參數1為棧指針,參數2為傳給待調用函數的參數數量, //參數3為期望返回的結果的數量,參數4為錯誤處理函數的索引(這個索引為壓入棧的函數索引,0表示沒有錯誤處理函數) int result = lua_pcall(L,0,LUA_MULTRET,0); //如果運行沒有錯誤,lua_pcall將返回0 if(!result) { printf_s("lua腳本運行成功\n"); } lua_close(L); return 0; }
lua1.cpp新建完畢後,我們在F:/LuaFile/路徑下新建一個lua1.lua文件,然後簡單的寫上一句打印
運行程序,看看結果
上面的程序使用了luaL_newstate、luaL_openlibs、luaL_loadfile、lua_pcall、lua_close這幾個c api,其中luaL_newstate用於在宿主程序中創建lua的虛擬機(棧);剛創建好的虛擬機是不具備lua的庫環境的,因此需要luaL_openlibs函數打開lua的標准庫;虛擬機的環境初始化完畢後,我們使用luaL_loadfile把lua1.lua文件的內容讀取出來,讀取後將內容視作代碼進行編譯,如果編譯成功,將編譯後的代碼塊壓入虛擬機中;壓入虛擬機中的代碼塊是可以被執行的,因此我們通過lua_pcall來執行這些代碼塊,壓入的代碼塊正是lua1.lua中的print("I am lua1"),因此控制台中輸出"Iam lua1";執行結束後,關閉虛擬機,至此,一個簡單的c api示例程序運行完畢。
(2)數據輸入
我們修改一下main函數裡面的內容
int _tmain(int argc, _TCHAR* argv[]) { char buff[256]; int loadError; int callError; //創建一個lua狀態機(虛擬棧),這個狀態機沒有包含預定義的lua庫函數 lua_State* L = luaL_newstate(); //打開標准庫,這樣狀態機可以使用lua庫函數,可以解釋lua腳本 luaL_openlibs(L); //使用fgets輸入流輸入數據,如果使用scanf_s會受到空格的影響 while(fgets(buff,sizeof(buff),stdin) != NULL) { //接收用戶輸入的數據,編譯為程序塊並壓入棧中,如果沒有錯誤,返回0;如果有錯誤,壓入一行錯誤信息的字符串 loadError =luaL_loadbuffer(L,buff,strlen(buff),"line"); if(loadError) { //打印這條錯誤信息,同時將錯誤信息壓桟 printf_s("%s\n",lua_tostring(L,-1)); //彈出這條錯誤信息,第二個參數是從棧頂彈出元素的個數 lua_pop(L,1); } //執行代碼塊,並將代碼塊彈出棧 callError = lua_pcall(L,0,0,0); if(callError) { printf_s("%s\n",lua_tostring(L,-1)); lua_pop(L,1); } } lua_close(L); return 0; }
運行結果
上述程序將每一行的輸入都動態編譯成了lua的代碼塊,再通過lua_pcall函數來執行這些代碼塊。
通過luaL_loadbuffer函數,上述代碼還可以寫成這樣子。
int _tmain(int argc, _TCHAR* argv[]) { char buff[256]; int loadError; int callError; //c++長字符串在每行後面用"\"接續 string luaBuff = "print('hello world');" \ "local a = 123;" \ "print(type(a));" \ "local b = 'ABC';" \ "print(type(b));" \ "function d(...)" \ "local str = 'abc^%&(';" \ "print(string.gsub(str,'%w',''));" \ "end;" \ "d();"; //創建一個lua狀態機(虛擬棧),這個狀態機沒有包含預定義的lua庫函數 lua_State* L = luaL_newstate(); //打開標准庫,這樣狀態機可以使用lua庫函數,可以解釋lua腳本 luaL_openlibs(L); //接收用戶輸入的數據,編譯為程序塊並壓入棧中,如果沒有錯誤,返回0;如果有錯誤,壓入一行錯誤信息的字符串 loadError =luaL_loadbuffer(L,luaBuff.c_str(),strlen(luaBuff.c_str()),"line"); if(loadError) { //打印這條錯誤信息,同時將錯誤信息壓桟 printf_s("%s\n",lua_tostring(L,-1)); //彈出這條錯誤信息,第二個參數是從棧頂彈出元素的個數 lua_pop(L,1); } //執行代碼塊,並將代碼塊彈出棧 callError = lua_pcall(L,0,0,0); if(callError) { printf_s("%s\n",lua_tostring(L,-1)); lua_pop(L,1); } lua_close(L); return 0; }
運行結果
可以看到,luaBuff這個字符串的內容也被當成是lua代碼塊來執行了。
(3)C++與lua間的通信
上述的例子都是直接加在的一個代碼塊去執行它,如果c++代碼中有函數fun1和fun2,lua的代碼中有函數fun3和fun4,現在需要在fun1中調用fun3,在fun4中調用fun2,那麼程序可以寫成這樣子。
lua1.cpp:
// lua1.cpp : 定義控制台應用程序的入口點。 // #include "stdafx.h" #include "iostream" #include "stdio.h" #include "string.h" //lua.h和lualib.h作為C代碼運行,在C++文件中使用lua.h和lualib.h,需使用extern命令實現混合編程,否則會提示無法解析外部函數 extern "C" { #include "lua.h" #include "lauxlib.h" #include "lualib.h" } using namespace std; #pragma comment(lib,"lua5.1.lib") void fun1(lua_State* L) { printf_s("I am fun1,I'll call fun3\n"); //在全局范圍內獲得"fun3"的這個元素,並將這個元素的內容壓入棧中 lua_getglobal(L,"fun3"); int callError; callError = lua_pcall(L,0,0,0); if(callError) { printf_s("%s\n",lua_tostring(L,-1)); printf_s("call fun3 error."); lua_pop(L,1); } } int fun2(lua_State* L) { printf_s("I am fun2~~~~~~~~~\n"); //返回值的個數為0 return 0; } int _tmain(int argc, _TCHAR* argv[]) { char buff[256]; int error; //創建一個lua狀態機(虛擬棧),這個狀態機沒有包含預定義的lua庫函數 lua_State* L = luaL_newstate(); //打開標准庫,這樣狀態機可以使用lua庫函數,可以解釋lua腳本 luaL_openlibs(L); char fileName[] = "F:/LuaFile/lua1.lua"; //加載lua文件,解釋器將lua文件的內容讀取出來,動態編譯為代碼塊 int loadFileError = luaL_loadfile(L,fileName); //打印錯誤信息 if(loadFileError) { printf_s("%s\n",lua_tostring(L,-1)); lua_pop(L,1); } //向棧中壓入c++的fun2 lua_pushcfunction(L,fun2); //在棧中給fun2命名為"fun2",這樣其他地方就能根據"fun2"這個字段索引到fun2的內容 lua_setglobal(L,"fun2"); //執行棧中的代碼塊 int callError = lua_pcall(L,0,0,0); if(callError) { printf_s("%s\n",lua_tostring(L,-1)); lua_pop(L,1); } fun1(L); lua_close(L); return 0; }
然後是lua1.lua:
執行結果
可以看到,c++中的fun1調用了lua中的fun3,而lua中的fun4調用了fun2,這樣就完成了最基本的交互。需要注意的是,lua_setglobal和lua_getglobal等方法需要在代碼塊被執行後(lua_pcall)才能生效。也就是說,如果只是把代碼塊加載進了棧中,但是不執行這些代碼塊,那麼是獲取不到棧中的這些全局變量的。
(4)函數的傳參、返回值
我們對(3)中的lua1.cpp修改一下
lua1.cpp:
// lua1.cpp : 定義控制台應用程序的入口點。 // #include "stdafx.h" #include "iostream" #include "stdio.h" #include "string.h" //lua.h和lualib.h作為C代碼運行,在C++文件中使用lua.h和lualib.h,需使用extern命令實現混合編程,否則會提示無法解析外部函數 extern "C" { #include "lua.h" #include "lauxlib.h" #include "lualib.h" } using namespace std; #pragma comment(lib,"lua5.1.lib") void fun1(lua_State* L) { printf_s("I am fun1,I'll call fun3\n"); //在全局范圍內獲得"fun3"的這個元素,並將這個元素的內容壓入棧中 lua_getglobal(L,"fun3"); //給fun3函數傳參 lua_pushnumber(L,2); lua_pushnumber(L,5); int callError; //由於有fun3有兩個參數,同時有一個返回值,因此lua_pcall寫成lua_pcall(L,2,1,0) //lua_pcall會執行fun3方法,同時把fun3和處於棧頂的兩個參數一起彈出棧,然後將fun3的返回值壓桟 callError = lua_pcall(L,2,1,0); if(callError) { printf_s("%s\n",lua_tostring(L,-1)); printf_s("call fun3 error."); lua_pop(L,1); } else { //讀出fun3的返回值,在棧頂 int returnNum = lua_tonumber(L,-1); printf_s("fun3 return num:%d\n",returnNum); } } int fun2(lua_State* L) { printf_s("I am fun2~~~~~~~~~\n"); //獲得當前棧中被調用的函數的第一個參數,也就是"the param from fun4" const char* paramStr = luaL_checkstring(L,1); printf_s("I get the str:"); printf_s(paramStr); printf_s("\n"); //把這個參數彈出棧 lua_pop(L,1); //把返回值壓入棧中 lua_pushstring(L,"fun2 getted the param."); //返回值的個數為1 return 1; } int _tmain(int argc, _TCHAR* argv[]) { char buff[256]; int error; //創建一個lua狀態機(虛擬棧),這個狀態機沒有包含預定義的lua庫函數 lua_State* L = luaL_newstate(); //打開標准庫,這樣狀態機可以使用lua庫函數,可以解釋lua腳本 luaL_openlibs(L); char fileName[] = "F:/LuaFile/lua1.lua"; //加載lua文件,解釋器將lua文件的內容讀取出來,動態編譯為代碼塊 int loadFileError = luaL_loadfile(L,fileName); //打印錯誤信息 if(loadFileError) { printf_s("%s\n",lua_tostring(L,-1)); lua_pop(L,1); } //向棧中壓入c++的fun2 lua_pushcfunction(L,fun2); //在棧中給fun2命名為"fun2",這樣其他地方就能根據"fun2"這個字段索引到fun2的內容 lua_setglobal(L,"fun2"); //執行棧中的代碼塊 int callError = lua_pcall(L,0,0,0); if(callError) { printf_s("%s\n",lua_tostring(L,-1)); lua_pop(L,1); } fun1(L); lua_close(L); return 0; }
然後修改一下lua1.lua的內容
執行程序,看看結果
可以看到c++的代碼和lua的代碼已經實現了相互的傳參和獲取返回值。
除了上述這些交互部分,還有許多種常見的數據交互,如c++中類、lua中的table等,這些部分我們在後面的篇章中再進行敘述,這篇文章就先這樣了,嗯 = =。