上一篇我們提到許多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等,這些部分我們在後面的篇章中再進行敘述,這篇文章就先這樣了,嗯 = =。