## 綁定Lua和C/C++的庫
* [CPPlua](http://sourceforge.net/projects/cpplua/)
* [tolua](http://www.tecgraf.puc-rio.br/~celes/tolua/)
* [tolua++](http://www.codenix.com/~tolua/)
* [luawrapper](http://www.d2-life.com/LBS/blogview.asp?logID=41)
* [luabind](http://luabind.sourceforge.net/)
* [luaplus](http://luaplus.org/)
## Lua調用C/C++
### 簡介
Lua(念“魯啊”)作為一門發展成熟的腳本語言,正在變得越來越流行。它也可以作為和C/C++執行腳本交互的語言。并且Lua的整個庫很小,Lua 5.1版本整個靜態鏈接的lua.dll才164KB,所以Lua很輕量,特別適合輕量級腳本嵌入。
這節要講Lua和C/C++的交互——Lua通過C/C++導出的dll來調用。
**LUA調用C文件中的函數方法**
* C中注冊函數
~~~
lua_pushcfunction(l, l_sin); //注冊在lua中使用的c函數l_sin
lua_setglobal(l, "mysin"); //設定綁定到lua中的名字為mysin
~~~
* C中提供的函數其定義要符合:
~~~
typedef int function(lua_State *L)
~~~
### [](https://github.com/andycai/luaprimer/blob/master/08.md#準備工作)準備工作
安裝完Lua,需要在Visual Studio中配置Lua路徑,使得你的編譯器能搜尋到。關于VS2010的配置,見我的博文《VS2010 C++目錄配置》一文。完成后新建一個Dll工程便可以了。
我們用一個在Lua中顯示Windows對話框的程序來簡要介紹一下,程序雖小,但五臟俱全。程序如下:
~~~
// 將一些有用的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 = "參數數目錯誤!";
static const char* const ERROR_ARGUMENT_TYPE = "參數類型錯誤!";
// 發生錯誤,報告錯誤
void ErrorMsg(lua_State* luaEnv, const char* const pszErrorInfo)
{
lua_pushstring(luaEnv, pszErrorInfo);
lua_error(luaEnv);
}
// 檢測函數調用參數個數是否正常
void CheckParamCount(lua_State* luaEnv, int paramCount)
{
// lua_gettop獲取棧中元素個數.
if (lua_gettop(luaEnv) != paramCount)
{
ErrorMsg(luaEnv, ERROR_ARGUMENT_COUNT);
}
}
// 顯示Windows對話框.
// @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;
// 檢測參數個數是否正確.
CheckParamCount(luaEnv, 2);
// 提取參數.
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);
}
// 返回值個數為0個.
return 0;
}
// 導出函數列表.
static luaL_Reg luaLibs[] =
{
{"ShowMsgBox", ShowMsgBox},
{NULL, NULL}
};
// Dll入口函數,Lua調用此Dll的入口函數.
extern "C" __declspec(dllexport)
int luaopen_WinFeature(lua_State* luaEnv)
{
const char* const LIBRARY_NAME = "WinFeature";
luaL_register(luaEnv, LIBRARY_NAME, luaLibs);
return 1;
}
~~~
### [](https://github.com/andycai/luaprimer/blob/master/08.md#程序解析)程序解析
首先我們包含Lua的頭文件,并鏈入庫文件。注意:Lua的頭文件為C風格,所以用external “C”來含入。在此例中,我們最終的導出函數為“ShowMsgBox”。
每一個導出函數的格式都為:
~~~
extern “C”int Export_Proc_Name(luaState* luaEnv);
~~~
其中,luaState*所指的結構中包含了Lua調用此Dll時必備的Lua環境。那么Lua向函數傳遞參數該怎么辦呢?實際上是用luaL_check[type]函數來完成的。如下:
~~~
const char* pHelloStr = luaL_checkstring(luaEnv, 1);
double value = luaL_checknumber(luaEnv, 2);
int ivalue = luaL_checkint(luaEnv, 3);
~~~
luaL_check系列函數的第二個參數是Lua調用該函數時傳遞參數從坐到右的順序(從1開始)。
然后我們看到,static的一個luaL_Reg的結構數組中包含了所有要導出的函數列表。最后通過luaopen_YourDllName的一個導出函數來完成一系列操作。YourDllName就是你最終的Dll的名字(不含擴展名)。因為你在Lua中調用此Dll時,Lua會根據此Dll名字找luaopen_YourDllName對應的函數,然后從此函數加載該Dll。
Dll入口函數格式如下:
~~~
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對應的庫名,以及luaL_Reg數組對應的導出列表來注冊到lua_State*對應的Lua環境中。
### [](https://github.com/andycai/luaprimer/blob/master/08.md#lua調用)Lua調用
那么我們要如何調用該Dll呢?首先,把該Dll放到你的Lua能搜尋到的目錄——當前目錄、Lua安裝目錄下的clibs目錄,然后通過require函數導入。
因為Lua中如果你的函數調用參數只有一個,并且該參數為字符串的話,函數調用時的括號是可以省略的,所以: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
>
~~~
可以看到,函數調用方式都是“包名.函數名”,而包名就是你的Dll的名字。我們可以用下面的方式查看一個包中的所有函數:
~~~
for k, v in pairs(PackageName) do
print(k, v)
end
~~~
然后我們調用WinFeature.ShowMsgBox函數:
~~~
> WinFeature.ShowMsgBox("Hello, this is a msgBox", "Tip")
~~~
會彈出對話框顯示內容為"Hello, this is a msgBox"和標題為"Tip"。
### [](https://github.com/andycai/luaprimer/blob/master/08.md#lua堆棧詳解)Lua堆棧詳解
唔,那么lua_State結構如何管理Lua運行環境的呢?Lua又是如何將參數傳遞到C/C++函數的呢?C/C++函數又如何返回值給Lua呢?……這一切,都得從Lua堆棧講起。
Lua在和C/C++交互時,Lua運行環境維護著一份堆棧——不是傳統意義上的堆棧,而是Lua模擬出來的。Lua與C/C++的數據傳遞都通過這份堆棧來完成,這份堆棧的代表就是lua_State*所指的那個結構。
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#堆棧結構解析)堆棧結構解析
堆棧通過lua_push系列函數向堆棧中壓入值,通過luaL_check系列從堆棧中獲取值。而用luaL_check系列函數時傳遞的參數索引,比如我們調用WinFeature.ShowMsgBox(“Hello”, “Tip”)函數時,棧結構如下:
棧頂 "Tip" 2或者-1 "Hello" 1或者-2 棧底
其中,參數在棧中的索引為參數從左到右的索引(從1開始),棧頂元素索引也可以從-1記起。棧中元素個數可以用lua_gettop來獲得,如果lua_gettop返回0,表示此棧為空。(lua_gettop這個函數名取得不怎么樣!)
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#提取參數)提取參數
luaL_check系列函數在獲取值的同時,檢測這個值是不是符合我們所期望的類型,如果不是,則拋出異常。所有這個系列函數如下:
~~~
luaL_checkany —— 檢測任何值(可以為nil)
luaL_checkint —— 檢測一個值是否為number(double),并轉換成int
luaL_checkinteger —— 檢測一個值是否為number(double),并轉換成lua_Integer(prtdiff_t),在我的機子上,ptrdiff_t被定義為int
luaL_checklong —— 檢測一個值是否為number(double),并轉換成long
luaL_checklstring —— 檢測一個值是否為string,并將字符串長度傳遞在[out]參數中返回
luaL_checknumber —— 檢測一個值是否為number(double)
luaL_checkstring —— 檢測一個值是否為string并返回
luaL_checkudata —— 檢測自定義類型
~~~
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#傳遞返回值)傳遞返回值
當我們要傳遞返回值給Lua時,可以用lua_push系列函數來完成。每一個導出函數都要返回一個int型整數,這個整數是你的導出函數的返回值的個數。而返回值通過lua_push系列函數壓入棧中。比如一個Add函數:
~~~
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代表返回值的個數。lua_push系列函數如下:
~~~
lua_pushboolean —— 壓入一個bool值
lua_pushcfunction —— 壓入一個lua_CFunction類型的C函數指針
lua_pushfstring —— 格式化一個string并返回,類似于sprintf
lua_pushinteger —— 壓入一個int
lua_pushlightuserdata —— 壓入自定義數據類型
lua_pushliteral —— 壓入一個字面值字符串
lua_pushlstring —— 壓入一個規定長度內的string
lua_pushnil —— 壓入nil值
lua_pushnumber —— 壓入lua_Number(double)值
lua_pushstring —— 壓入一個string
lua_pushthread —— 壓入一個所傳遞lua_State所對應的線程,如果此線程是主線程,則返回1
lua_pushvalue —— 將所傳遞索引處的值復制一份壓入棧頂
lua_pushvfstring —— 類似lua_pushfstring
~~~
通過這些函數,我們可以靈活的使用C/C++的高性能特性,來導出函數供Lua調用。
## [](https://github.com/andycai/luaprimer/blob/master/08.md#cc調用lua腳本)C/C++調用Lua腳本
### [](https://github.com/andycai/luaprimer/blob/master/08.md#簡介-1)簡介
**C調用LUA文件中的函數方法**
~~~
lua_getglobal(L, <function name>) //獲取lua中的函數
lua_push*() //調用lua_push系列函數,把輸入參數壓棧。例如lua_pushnumber(L, x)
lua_pcall(L, <arguments nums>, <return nums>, <錯誤處理函數地址>)
~~~
例如:
~~~
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);
~~~
上一節介紹了如何在Lua中調用C/C++代碼,本節介紹如何在C/C++中調用Lua腳本。本節介紹一個例子,通過Lua來生成一個XML格式的便簽。便簽格式如下:
~~~
<?xml version="1.0" encoding="utf-8" ?>
<note>
<fromName>發送方姓名</fromName>
<toName>接收方姓名</toName>
<sendTime>發送時間</sendTime>
<msgContent>便簽內容</msgContent>
</note>
~~~
我們通過C/C++來輸入這些信息,然后讓Lua來生成這樣一個便簽文件。
### [](https://github.com/andycai/luaprimer/blob/master/08.md#lua代碼)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來打開/關閉XML文件。generateNoteXML全局函數接受發送方姓名、接收方姓名、便簽內容,生成一個XML便簽文件。便簽發送時間通過Lua標準庫os.time()函數來獲取。writeNestedLabel函數根據當前XML的縮進數目來規范XML輸出格式。此文件很好理解,不再贅述。
### [](https://github.com/andycai/luaprimer/blob/master/08.md#c調用lua腳本)C++調用Lua腳本
~~~
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#pragma comment(lib, "lua.lib")
};
#include <iostream>
#include <string>
using namespace std;
// 初始化Lua環境.
lua_State* initLuaEnv()
{
lua_State* luaEnv = lua_open();
luaopen_base(luaEnv);
luaL_openlibs(luaEnv);
return luaEnv;
}
// 加載Lua文件到Lua運行時環境中
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;
}
// 獲取全局函數
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運行時環境.
lua_State* luaEnv = initLuaEnv();
if (!luaEnv)
{
return -1;
}
// 加載腳本到Lua環境中.
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);
// 將要調用的函數和函數調用參數入棧.
lua_getglobal(luaEnv, "generateNoteXML");
lua_pushstring(luaEnv, fromName.c_str());
lua_pushstring(luaEnv, toName.c_str());
lua_pushstring(luaEnv, msgContent.c_str());
// 調用Lua函數(3個參數,一個返回值).
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運行時環境.
lua_close(luaEnv);
system("pause");
return 0;
}
~~~
### [](https://github.com/andycai/luaprimer/blob/master/08.md#代碼解析)代碼解析
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#初始化lua運行時環境)初始化Lua運行時環境
lua_State*所指向的結構中封裝了Lua的運行時環境。我們用initLuaEnv這個函數來初始化。lua_open函數用來獲取一個新環境,然后我們用luaopen_base打開Lua的基礎庫,然后用luaL_openlibs打開Lua的io、string、math、table等高級庫。
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#加載lua文件)加載Lua文件
然后我們用luaL_loadfile和lua_pcall來將一個Lua腳本加載到對應的Lua運行時環境中——注意:并不自動執行,只是加載。這兩個函數如果返回非0,表示加載失敗——你的Lua腳本文件可能未找到或Lua腳本有語法錯誤……
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#加載lua函數)加載Lua函數
我們用lua_getglobal函數將Lua腳本中的全局函數、全局變量等入棧,放在棧頂。
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#壓入lua函數調用參數)壓入Lua函數調用參數
我們用lua_push系列函數來將要調用函數所需參數全部入棧,入棧順序為Lua函數對應參數從左到右的順序。
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#調用lua函數)調用Lua函數
最后用lua_pcall來調用函數。Lua_pcall函數原型如下:
~~~
int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc);
~~~
其中,L為此函數執行的Lua環境,nargs為此函數所需的參數個數,nresults為此函數的返回值個數,errfunc為發生錯誤時錯誤處理函數在堆棧中的索引。
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#獲取lua函數返回值)獲取Lua函數返回值
然后,我們可以通過檢測棧頂的位置(從-1開始),來獲取返回值。獲取返回值后,用lua_pop將棧頂元素出棧——出棧個數為返回值個數。
#### [](https://github.com/andycai/luaprimer/blob/master/08.md#釋放lua環境)釋放Lua環境
最后,通過lua_close函數來關閉Lua環境并釋放資源。
### [](https://github.com/andycai/luaprimer/blob/master/08.md#運行結果)運行結果
我們將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>
~~~
## [](https://github.com/andycai/luaprimer/blob/master/08.md#c-作為動態庫文件被-lua-調用)C 作為動態庫文件被 Lua 調用
### [](https://github.com/andycai/luaprimer/blob/master/08.md#cc中的入口函數定義)C/C++中的入口函數定義
一定是要定義成: 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;
}
~~~
### [](https://github.com/andycai/luaprimer/blob/master/08.md#動態庫要供lua調用的function)動態庫要供LUA調用的function
其定義要符合:
~~~
typedef int function(lua_State *L)
~~~
### [](https://github.com/andycai/luaprimer/blob/master/08.md#在動態庫調用lua注冊)在動態庫調用LUA注冊
將要調用的函數放到這個結構體里:
~~~
struct luaL_Reg lib[] ={}
~~~
在動態庫的入口函數里調用luaL_register將這個結構體注冊,在這個入口函數注冊結構體時,要注冊成:
~~~
luaL_register(L,"XXX",lib);
~~~
### [](https://github.com/andycai/luaprimer/blob/master/08.md#在寫腳本的時候使用requirexxx)在寫腳本的時候,使用require("XXX")
就是入口函數的luaopen_后面的XXX,注意大小寫敏感
### [](https://github.com/andycai/luaprimer/blob/master/08.md#編譯生成的動態庫命令成xxxso或xxxdllwin)編譯生成的動態庫命令成XXX.so或XXX.dll(win)
同入口函數的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函數替換為:
~~~
int luaopen_testlib(lua_State *L)
{
lua_createmeta(L,"testlib",lib);
return 1;
}
~~~