Lua(念“魯啊”)作為一門發(fā)展成熟的腳本語言,正在變得越來越流行。它也可以作為和C/C++執(zhí)行腳本交互的語言。并且Lua的整個庫很小,Lua 5.1版本整個靜態(tài)鏈接的lua.dll才164KB,所以Lua很輕量,特別適合輕量級腳本嵌入。
這節(jié)要講Lua和C/C++的交互——Lua通過C/C++導出的dll來調(diào)用。
LUA調(diào)用C文件中的函數(shù)方法
lua_pushcfunction(l, l_sin); //注冊在lua中使用的c函數(shù)l_sin
lua_setglobal(l, "mysin"); //設定綁定到lua中的名字為mysin
typedef int function(lua_State *L)
安裝完Lua,需要在Visual Studio中配置Lua路徑,使得你的編譯器能搜尋到。關(guān)于VS2010的配置,見我的博文《VS2010 C++目錄配置》一文。完成后新建一個Dll工程便可以了。
我們用一個在Lua中顯示W(wǎng)indows對話框的程序來簡要介紹一下,程序雖小,但五臟俱全。程序如下:
// 將一些有用的Win32特性導出
// 以便在Lua中使用
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#pragma comment(lib, "lua.lib")
};
#include <Windows.h>
#include <iostream>
using namespace std;
static const char* const ERROR_ARGUMENT_COUNT = "參數(shù)數(shù)目錯誤!";
static const char* const ERROR_ARGUMENT_TYPE = "參數(shù)類型錯誤!";
// 發(fā)生錯誤,報告錯誤
void ErrorMsg(lua_State* luaEnv, const char* const pszErrorInfo)
{
lua_pushstring(luaEnv, pszErrorInfo);
lua_error(luaEnv);
}
// 檢測函數(shù)調(diào)用參數(shù)個數(shù)是否正常
void CheckParamCount(lua_State* luaEnv, int paramCount)
{
// lua_gettop獲取棧中元素個數(shù).
if (lua_gettop(luaEnv) != paramCount)
{
ErrorMsg(luaEnv, ERROR_ARGUMENT_COUNT);
}
}
// 顯示W(wǎng)indows對話框.
// @param [in] pszMessage string 1
// @param [in] pszCaption string 2
extern "C" int ShowMsgBox(lua_State* luaEnv)
{
const char* pszMessage = 0;
const char* pszCaption = 0;
// 檢測參數(shù)個數(shù)是否正確.
CheckParamCount(luaEnv, 2);
// 提取參數(shù).
pszMessage = luaL_checkstring(luaEnv, 1);
pszCaption = luaL_checkstring(luaEnv, 2);
if (pszCaption && pszMessage)
{
::MessageBox(
NULL,
pszMessage,
pszCaption,
MB_OK | MB_ICONINFORMATION
);
}
else
{
ErrorMsg(luaEnv, ERROR_ARGUMENT_TYPE);
}
// 返回值個數(shù)為0個.
return 0;
}
// 導出函數(shù)列表.
static luaL_Reg luaLibs[] =
{
{"ShowMsgBox", ShowMsgBox},
{NULL, NULL}
};
// Dll入口函數(shù),Lua調(diào)用此Dll的入口函數(shù).
extern "C" __declspec(dllexport)
int luaopen_WinFeature(lua_State* luaEnv)
{
const char* const LIBRARY_NAME = "WinFeature";
luaL_register(luaEnv, LIBRARY_NAME, luaLibs);
return 1;
}
首先我們包含Lua的頭文件,并鏈入庫文件。注意:Lua的頭文件為C風格,所以用external “C”來含入。在此例中,我們最終的導出函數(shù)為“ShowMsgBox”。
每一個導出函數(shù)的格式都為:
extern “C”int Export_Proc_Name(luaState* luaEnv);
其中,luaState*所指的結(jié)構(gòu)中包含了Lua調(diào)用此Dll時必備的Lua環(huán)境。那么Lua向函數(shù)傳遞參數(shù)該怎么辦呢?實際上是用luaL_check[type]函數(shù)來完成的。如下:
const char* pHelloStr = luaL_checkstring(luaEnv, 1);
double value = luaL_checknumber(luaEnv, 2);
int ivalue = luaL_checkint(luaEnv, 3);
luaL_check系列函數(shù)的第二個參數(shù)是Lua調(diào)用該函數(shù)時傳遞參數(shù)從坐到右的順序(從1開始)。
然后我們看到,static的一個luaL_Reg的結(jié)構(gòu)數(shù)組中包含了所有要導出的函數(shù)列表。最后通過luaopen_YourDllName的一個導出函數(shù)來完成一系列操作。YourDllName就是你最終的Dll的名字(不含擴展名)。因為你在Lua中調(diào)用此Dll時,Lua會根據(jù)此Dll名字找luaopen_YourDllName對應的函數(shù),然后從此函數(shù)加載該Dll。
Dll入口函數(shù)格式如下:
extern "C" __declspec(dllexport)
int luaopen_WinFeature(lua_State* luaEnv)
{
const char* const LIBRARY_NAME = "WinFeature";
luaL_register(luaEnv, LIBRARY_NAME, luaLibs);
return 1;
}
我們通過luaL_register將LIBRARY_NAME對應的庫名,以及l(fā)uaL_Reg數(shù)組對應的導出列表來注冊到lua_State*對應的Lua環(huán)境中。
那么我們要如何調(diào)用該Dll呢?首先,把該Dll放到你的Lua能搜尋到的目錄——當前目錄、Lua安裝目錄下的clibs目錄,然后通過require函數(shù)導入。
因為Lua中如果你的函數(shù)調(diào)用參數(shù)只有一個,并且該參數(shù)為字符串的話,函數(shù)調(diào)用時的括號是可以省略的,所以:require(“YourLibName”)和requir“YourLibName”都是合法的。我們把剛剛生成的WinFeature.dll文件拷貝到C盤下,然后在C盤啟動Lua。示例如下:
> require "WinFeature"
> for k, v in pairs(WinFeature) do
>> print(k, v)
>> end
ShowMsgBox functon:0028AB90
>
可以看到,函數(shù)調(diào)用方式都是“包名.函數(shù)名”,而包名就是你的Dll的名字。我們可以用下面的方式查看一個包中的所有函數(shù):
for k, v in pairs(PackageName) do
print(k, v)
end
然后我們調(diào)用WinFeature.ShowMsgBox函數(shù):
> WinFeature.ShowMsgBox("Hello, this is a msgBox", "Tip")
會彈出對話框顯示內(nèi)容為"Hello, this is a msgBox"和標題為"Tip"。
唔,那么lua_State結(jié)構(gòu)如何管理Lua運行環(huán)境的呢?Lua又是如何將參數(shù)傳遞到C/C++函數(shù)的呢?C/C++函數(shù)又如何返回值給Lua呢?……這一切,都得從Lua堆棧講起。
Lua在和C/C++交互時,Lua運行環(huán)境維護著一份堆?!皇莻鹘y(tǒng)意義上的堆棧,而是Lua模擬出來的。Lua與C/C++的數(shù)據(jù)傳遞都通過這份堆棧來完成,這份堆棧的代表就是lua_State*所指的那個結(jié)構(gòu)。
堆棧通過lua_push系列函數(shù)向堆棧中壓入值,通過luaL_check系列從堆棧中獲取值。而用luaL_check系列函數(shù)時傳遞的參數(shù)索引,比如我們調(diào)用WinFeature.ShowMsgBox(“Hello”, “Tip”)函數(shù)時,棧結(jié)構(gòu)如下:
棧頂 "Tip" 2或者-1 "Hello" 1或者-2 棧底
其中,參數(shù)在棧中的索引為參數(shù)從左到右的索引(從1開始),棧頂元素索引也可以從-1記起。棧中元素個數(shù)可以用lua_gettop來獲得,如果lua_gettop返回0,表示此棧為空。(lua_gettop這個函數(shù)名取得不怎么樣?。?/p>
luaL_check系列函數(shù)在獲取值的同時,檢測這個值是不是符合我們所期望的類型,如果不是,則拋出異常。所有這個系列函數(shù)如下:
luaL_checkany —— 檢測任何值(可以為nil)
luaL_checkint —— 檢測一個值是否為number(double),并轉(zhuǎn)換成int
luaL_checkinteger —— 檢測一個值是否為number(double),并轉(zhuǎn)換成lua_Integer(prtdiff_t),在我的機子上,ptrdiff_t被定義為int
luaL_checklong —— 檢測一個值是否為number(double),并轉(zhuǎn)換成long
luaL_checklstring —— 檢測一個值是否為string,并將字符串長度傳遞在[out]參數(shù)中返回
luaL_checknumber —— 檢測一個值是否為number(double)
luaL_checkstring —— 檢測一個值是否為string并返回
luaL_checkudata —— 檢測自定義類型
當我們要傳遞返回值給Lua時,可以用lua_push系列函數(shù)來完成。每一個導出函數(shù)都要返回一個int型整數(shù),這個整數(shù)是你的導出函數(shù)的返回值的個數(shù)。而返回值通過lua_push系列函數(shù)壓入棧中。比如一個Add函數(shù):
extern “C” int Add(lua_State* luaEnv)
{
CheckParamCount(luaEnv, 2);
double left = luaL_checknumber(luaEnv, 1);
double right = luaL_checknumber(luaEnv, 2);
double result = left + right;
lua_pushnumber(luaEnv, result);
return 1;
}
可以看出,我們用lua_pushnumber把返回值壓入棧,最后返回1——1代表返回值的個數(shù)。lua_push系列函數(shù)如下:
lua_pushboolean —— 壓入一個bool值
lua_pushcfunction —— 壓入一個lua_CFunction類型的C函數(shù)指針
lua_pushfstring —— 格式化一個string并返回,類似于sprintf
lua_pushinteger —— 壓入一個int
lua_pushlightuserdata —— 壓入自定義數(shù)據(jù)類型
lua_pushliteral —— 壓入一個字面值字符串
lua_pushlstring —— 壓入一個規(guī)定長度內(nèi)的string
lua_pushnil —— 壓入nil值
lua_pushnumber —— 壓入lua_Number(double)值
lua_pushstring —— 壓入一個string
lua_pushthread —— 壓入一個所傳遞lua_State所對應的線程,如果此線程是主線程,則返回1
lua_pushvalue —— 將所傳遞索引處的值復制一份壓入棧頂
lua_pushvfstring —— 類似lua_pushfstring
通過這些函數(shù),我們可以靈活的使用C/C++的高性能特性,來導出函數(shù)供Lua調(diào)用。
C調(diào)用LUA文件中的函數(shù)方法
lua_getglobal(L, <function name>) //獲取lua中的函數(shù)
lua_push*() //調(diào)用lua_push系列函數(shù),把輸入?yún)?shù)壓棧。例如lua_pushnumber(L, x)
lua_pcall(L, <arguments nums>, <return nums>, <錯誤處理函數(shù)地址>)
例如:
lua_settop(m_pLua,0);
lua_getglobal(m_pLua,"mainlogic");
lua_pushlstring(m_pLua,(char*)msg.getBuf(),msg.size());
int ret = 0;
ret = lua_pcall(m_pLua,1,4,0);
上一節(jié)介紹了如何在Lua中調(diào)用C/C++代碼,本節(jié)介紹如何在C/C++中調(diào)用Lua腳本。本節(jié)介紹一個例子,通過Lua來生成一個XML格式的便簽。便簽格式如下:
<?xml version="1.0" encoding="utf-8" ?>
<note>
<fromName>發(fā)送方姓名</fromName>
<toName>接收方姓名</toName>
<sendTime>發(fā)送時間</sendTime>
<msgContent>便簽內(nèi)容</msgContent>
</note>
我們通過C/C++來輸入這些信息,然后讓Lua來生成這樣一個便簽文件。
xmlHead = '<?xml version="1.0" encoding="utf-8" ?>\n'
-- Open note file to wriet.
function openNoteFile(fileName)
return io.open(fileName, "w")
end
-- Close writed note file.
function closeNoteFile(noteFile)
noteFile:close()
end
function writeNestedLabel(ioChanel, label, nestCnt)
if nestCnt == 0 then
ioChanel:write(label)
return
end
for i = 1, nestCnt do
ioChanel:write("\t")
end
ioChanel:write(label)
end
function generateNoteXML(fromName, toName, msgContent)
local noteFile = openNoteFile(fromName .. "_" .. toName .. ".xml")
if not noteFile then
return false
end
noteFile:write(xmlHead)
noteFile:write("<note>\n")
local currNestCnt = 1
writeNestedLabel(noteFile, "<fromName>", currNestCnt)
noteFile:write(fromName)
writeNestedLabel(noteFile, "</fromName>\n", 0)
writeNestedLabel(noteFile, "<toName>", currNestCnt)
noteFile:write(toName)
writeNestedLabel(noteFile, "</toName>\n", 0)
local sendTime = os.time()
writeNestedLabel(noteFile, "<sendTime>", currNestCnt)
noteFile:write(sendTime)
writeNestedLabel(noteFile, "</sendTime>\n", 0)
writeNestedLabel(noteFile, "<msgContent>", currNestCnt)
noteFile:write(msgContent)
writeNestedLabel(noteFile, "</msgContent>\n", 0)
noteFile:write("</note>\n")
closeNoteFile(noteFile)
return true
end
我們通過openNoteFile和closeNoteFile來打開/關(guān)閉XML文件。generateNoteXML全局函數(shù)接受發(fā)送方姓名、接收方姓名、便簽內(nèi)容,生成一個XML便簽文件。便簽發(fā)送時間通過Lua標準庫os.time()函數(shù)來獲取。writeNestedLabel函數(shù)根據(jù)當前XML的縮進數(shù)目來規(guī)范XML輸出格式。此文件很好理解,不再贅述。
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#pragma comment(lib, "lua.lib")
};
#include <iostream>
#include <string>
using namespace std;
// 初始化Lua環(huán)境.
lua_State* initLuaEnv()
{
lua_State* luaEnv = lua_open();
luaopen_base(luaEnv);
luaL_openlibs(luaEnv);
return luaEnv;
}
// 加載Lua文件到Lua運行時環(huán)境中
bool loadLuaFile(lua_State* luaEnv, const string& fileName)
{
int result = luaL_loadfile(luaEnv, fileName.c_str());
if (result)
{
return false;
}
result = lua_pcall(luaEnv, 0, 0, 0);
return result == 0;
}
// 獲取全局函數(shù)
lua_CFunction getGlobalProc(lua_State* luaEnv, const string& procName)
{
lua_getglobal(luaEnv, procName.c_str());
if (!lua_iscfunction(luaEnv, 1))
{
return 0;
}
return lua_tocfunction(luaEnv, 1);
}
int main()
{
// 初始化Lua運行時環(huán)境.
lua_State* luaEnv = initLuaEnv();
if (!luaEnv)
{
return -1;
}
// 加載腳本到Lua環(huán)境中.
if (!loadLuaFile(luaEnv, ".\\GenerateNoteXML.lua"))
{
cout<<"Load Lua File FAILED!"<<endl;
return -1;
}
// 獲取Note信息.
string fromName;
string toName;
string msgContent;
cout<<"Enter your name:"<<endl;
cin>>fromName;
cout<<"\nEnter destination name:"<<endl;
cin>>toName;
cout<<"\nEnter message content:"<<endl;
getline(cin, msgContent);
getline(cin, msgContent);
// 將要調(diào)用的函數(shù)和函數(shù)調(diào)用參數(shù)入棧.
lua_getglobal(luaEnv, "generateNoteXML");
lua_pushstring(luaEnv, fromName.c_str());
lua_pushstring(luaEnv, toName.c_str());
lua_pushstring(luaEnv, msgContent.c_str());
// 調(diào)用Lua函數(shù)(3個參數(shù),一個返回值).
lua_pcall(luaEnv, 3, 1, 0);
// 獲取返回值.
if (lua_isboolean(luaEnv, -1))
{
int success = lua_toboolean(luaEnv, -1);
if (success)
{
cout<<"\nGenerate Note File Successful!"<<endl;
}
}
// 將返回值出棧.
lua_pop(luaEnv, 1);
// 釋放Lua運行時環(huán)境.
lua_close(luaEnv);
system("pause");
return 0;
}
lua_State*所指向的結(jié)構(gòu)中封裝了Lua的運行時環(huán)境。我們用initLuaEnv這個函數(shù)來初始化。lua_open函數(shù)用來獲取一個新環(huán)境,然后我們用luaopen_base打開Lua的基礎(chǔ)庫,然后用luaL_openlibs打開Lua的io、string、math、table等高級庫。
然后我們用luaL_loadfile和lua_pcall來將一個Lua腳本加載到對應的Lua運行時環(huán)境中——注意:并不自動執(zhí)行,只是加載。這兩個函數(shù)如果返回非0,表示加載失敗——你的Lua腳本文件可能未找到或Lua腳本有語法錯誤……
我們用lua_getglobal函數(shù)將Lua腳本中的全局函數(shù)、全局變量等入棧,放在棧頂。
我們用lua_push系列函數(shù)來將要調(diào)用函數(shù)所需參數(shù)全部入棧,入棧順序為Lua函數(shù)對應參數(shù)從左到右的順序。
最后用lua_pcall來調(diào)用函數(shù)。Lua_pcall函數(shù)原型如下:
int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc);
其中,L為此函數(shù)執(zhí)行的Lua環(huán)境,nargs為此函數(shù)所需的參數(shù)個數(shù),nresults為此函數(shù)的返回值個數(shù),errfunc為發(fā)生錯誤時錯誤處理函數(shù)在堆棧中的索引。
然后,我們可以通過檢測棧頂?shù)奈恢茫◤?1開始),來獲取返回值。獲取返回值后,用lua_pop將棧頂元素出?!鰲€數(shù)為返回值個數(shù)。
最后,通過lua_close函數(shù)來關(guān)閉Lua環(huán)境并釋放資源。
我們將GenerateNoteXML.lua腳本和最終的C++生成的GenerateNoteXML.exe放在同一路徑下,并運行GenerateNoteXML.exe,在此目錄下會生成一個XML文件。如下:
Enter your name:
Jack
Enter destnation name:
Joe
Enter message content:
Hello, Can you help me?
Generate Note File Successful!
生成的arnozhang_YaFengZhang.xml文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<note>
<fromName>Jack</fromName>
<toName>Joe</toName>
<sendTime>1317971623</sendTime>
<msgContent>Hello, Can you help me?</msgContent>
</note>
一定是要定義成: luaopen_(dll或so文件的文件名稱),(dll或so文件的文件名稱)必須和dll或so文件名稱保持一致。
例如(C++ windows情況):
#ifdef _WIN32
#define _EXPORT extern "C" __declspec(dllexport)
#else //unix/linux
#define _EXPORT extern "C"
#endif
_EXPORT int luaopen_capi_mytestlib(lua_State *L)
{
struct luaL_reg driver[] = {
{"average", average1},
{NULL, NULL},};
luaL_register(L, "mylib", driver);
//luaL_openlib(L, "mylib", driver, 0);
return 1;
}
其定義要符合:
typedef int function(lua_State *L)
將要調(diào)用的函數(shù)放到這個結(jié)構(gòu)體里:
struct luaL_Reg lib[] ={}
在動態(tài)庫的入口函數(shù)里調(diào)用luaL_register將這個結(jié)構(gòu)體注冊,在這個入口函數(shù)注冊結(jié)構(gòu)體時,要注冊成:
luaL_register(L,"XXX",lib);
就是入口函數(shù)的luaopen_后面的XXX,注意大小寫敏感
同入口函數(shù)的luaopen_后面的XXX一致
示例:
C文件如下:
#include <stdio.h>
#include "lua/lua.h"
#include "lua/lualib.h"
#include "lua/lauxlib.h"
static int add(lua_State *L)
{
int a,b,c;
a = lua_tonumber(L,1);
b = lua_tonumber(L,2);
c = a+b;
lua_pushnumber(L,c);
printf("test hello!!!\r\n");
return 1;
}
static const struct luaL_Reg lib[] =
{
{"testadd",add},
{NULL,NULL}
};
int luaopen_testlib(lua_State *L)
{
luaL_register(L,"testlib",lib);
return 1;
}
編譯: gcc test.c -fPIC -shared -o testlib.so
lua腳本編寫:
require("testlib")
c = testlib.testadd(15,25)
print("The result is ",c);
示例:
int lua_createmeta (lua_State *L, const char *name, const luaL_reg *methods) {
if (!luaL_newmetatable (L, name))
return 0;
luaL_openlib (L, NULL, methods, 0);
lua_pushliteral (L, "__gc");
lua_pushcfunction (L, methods->func);
lua_settable (L, -3);
lua_pushliteral (L, "__index");
lua_pushvalue (L, -2);
lua_settable (L, -3);
lua_pushliteral (L, "__metatable");
lua_pushliteral (L, "you're not allowed to get this metatable");
lua_settable (L, -3);
return 1;
}
示例中的luaopen_testlib函數(shù)替換為:
int luaopen_testlib(lua_State *L)
{
lua_createmeta(L,"testlib",lib);
return 1;
}
更多建議: