<--!版權(quán)所有foruok,轉(zhuǎn)載注明出處!-->
從lua調(diào)用C++函數(shù)和對(duì)象
利用LuaPlus可以方便的從C++中調(diào)用lua腳本,翻過(guò)也一樣。通過(guò)注冊(cè)函數(shù)或類對(duì)象,lua便可以訪問(wèn)C++。
一、C風(fēng)格函數(shù)注冊(cè)
Lua提供了C風(fēng)格的回調(diào)函數(shù)注冊(cè),該函數(shù)原型如下:
int
Callback(LuaState
*
state);
無(wú)論是全局函數(shù)、類非虛函數(shù)、類虛函數(shù),只要符合上面的原型,都可以向Lua注冊(cè)。我們以全局函數(shù)為例,下面是我們提供的一個(gè)回調(diào)函數(shù)CStyleAddFunc:
int
CStyleAddFunc(LuaState
*
state)
...
{
LuaStackargs(state);
if
(args[
1
].IsNumber()
&&
args[
2
].IsNumber())
...
{
state
->
PushNumber(args[
1
].GetInteger()
+
args[
2
].GetInteger());
return
1
;
}
return
0
;
}
在回調(diào)函數(shù)中,我們通過(guò)棧來(lái)訪問(wèn)參數(shù),棧中可以存貯多個(gè)參數(shù),
LuaStackargs(state);語(yǔ)句獲取棧對(duì)象供后續(xù)訪問(wèn)。 接下來(lái)判斷參數(shù)是否是數(shù)字,如果兩個(gè)參數(shù)都是數(shù)字,那么進(jìn)行加操作,將結(jié)果壓入棧中,將壓入棧中的數(shù)據(jù)的個(gè)數(shù)返回。注意,返回值代表壓入棧中的元素的個(gè)數(shù),而不是某種計(jì)算結(jié)果或其它意義的返回值。通過(guò)改變返回值來(lái)查看程序的輸出,這樣可以對(duì)返回值的含義有個(gè)感性的了解。 要注冊(cè)回到只需調(diào)用Register函數(shù)即可,這在第一篇中已經(jīng)用到。下面是測(cè)試函數(shù):
void
TestCFunctionCallBack()
...
{
LuaStateOwnerstate;
//
"print"needthis
state
->
OpenLibs();
//
registermyfunctionCStyleAddFunctoAdd
state
->
GetGlobals().Register(
"
Add
"
,CStyleAddFunc);
//
callmyfunctionandprinttheresult
state
->
DoString(
"
ret=Add(1,5);print(ret)
"
);
}
state
->
DoString(
"
ret=Add(1,5);print(ret)
"
);該句用來(lái)從執(zhí)行Lua命令串。我們先調(diào)用Add并將結(jié)果賦值給ret變量,然后打印ret的值。 main函數(shù)如下:
int
_tmain(
int
argc,_TCHAR
*
argv[])
...
{
TestCFunctionCallBack();
return
0
;
}
編譯運(yùn)行,一切OK。 我們也可以從Lua腳本文件中調(diào)用注冊(cè)的回調(diào)函數(shù),
第一篇
中有演示。 要注冊(cè)類的成員函數(shù),則需要調(diào)用Register的另一種形式
Register( const char* funcName, const Callee& callee, int (Callee::*func)(LuaState*), int nupvalues = 0 );
,提供類實(shí)例指針和函數(shù)即可完成注冊(cè)。下面是示例代碼:
class
CTestCallBack
...
{
public
:
int
NonVirtualFunc(LuaState
*
state)
...
{
LuaStackargs(state);
printf(
"
Innon-virtualmemberfunction.nomsg.
"
);
return
0
;
}
int
virtual
VirtualFunc(LuaState
*
state)
...
{
LuaStackargs(state);
printf(
"
Invirtualmemberfunction.msg=%s
"
,args[
1
].GetString());
return
0
;
}
}
;
void
TestClassMemberFuncReg()
...
{
LuaStateOwnerstate;
//
"print"needthis
state
->
OpenLibs();
LuaObjectglobalobj
=
state
->
GetGlobals();
CTestCallBacktcb;
globalobj.Register(
"
MemberFunc
"
,tcb,
&
CTestCallBack::NonVirtualFunc);
state
->
DoString(
"
MemberFunc()
"
);
globalobj.Register(
"
VirMemberFunc
"
,tcb,
&
CTestCallBack::VirtualFunc);
state
->
DoString(
"
VirMemberFunc('Hi,myboy')
"
);
}
修改一下main函數(shù),將
TestClassMemberFuncReg()加進(jìn)去就可以看效果了。
二、任意形式C++函數(shù)注冊(cè)
LuaPlus提供了
RegisterDirect()
來(lái)直接注冊(cè)任意形式的函數(shù),這樣更為直接,不必受限于上述的函數(shù)原型,使用起來(lái)很方便。同樣此函數(shù)像Register一樣,可以注冊(cè)類的成員函數(shù)(也需要顯示指定this指針)。下面是代碼:
float
Add(
float
num1,
float
num2)
...
{
return
num1
+
num2;
}
class
CForRegDirect
...
{
public
:
int
Sum(
int
a,
int
b,
int
c)
...
{
return
a
+
b
+
c;
}
//
constisnecessary
virtual
void
SeeMessage(
const
char
*
msg)
...
{
printf(
"
msg=%s
"
,msg);
}
}
;
void
TestRegisterDirect()
...
{
LuaStateOwnerstate;
state
->
OpenLibs();
LuaObjectgobj
=
state
->
GetGlobals();
//
registerglobalfunctiondirectly
gobj.RegisterDirect(
"
Add
"
,Add);
state
->
DoString(
"
print(Add(1.5,2.3))
"
);
//
registermemberfunction
CForRegDirectforobj;
gobj.RegisterDirect(
"
MemberSum
"
,forobj,CForRegDirect::Sum);
state
->
DoString(
"
print(MemberSum(1,2,7))
"
);
gobj.RegisterDirect(
"
VirCMsg
"
,forobj,CForRegDirect::SeeMessage);
state
->
DoString(
"
print(VirCMsg('haha,Doyouseeme?'))
"
);
}
三、注冊(cè)函子對(duì)象
上面兩節(jié)的方式可以實(shí)現(xiàn)簡(jiǎn)單的回調(diào)注冊(cè),注冊(cè)類的成員函數(shù)時(shí)需要顯式提供類指針,不適合用于映射C++中的類結(jié)構(gòu)。
RegisterObjectFunctor()和元表(metatable)結(jié)合,提供了一種新的方法
。我們不需要在注冊(cè)函數(shù)時(shí)顯式的提供this指針,作為替代,this指針可以從調(diào)用者的userdata或__object成員獲取。 元表(metatable)是一個(gè)普通的表對(duì)象,它定義了一些可以被重寫(xiě)的操作,如add,sub,mul,index,call等,這些操作以"__"開(kāi)頭,如__add,__index等。加入你重寫(xiě)了__add,那么在執(zhí)行add操作時(shí)就會(huì)調(diào)用你自己定義的__add操作。這種特性可以用來(lái)模擬C++中的類對(duì)象,注冊(cè)函子對(duì)象正是利用了這種特性來(lái)實(shí)現(xiàn)的。 下面我們將一個(gè)C++類映射到Lua中。類代碼如下:
class
CMultiObject
...
{
public
:
CMultiObject(
int
num):m_num(num)
...
{
}
int
Print(LuaState
*
state)
...
{
printf(
"
%d
"
,m_num);
return
0
;
}
protected
:
int
m_num;
}
;
void
TestRegObjectDispatchFunctor()
...
{
LuaStateOwnerstate;
state
->
OpenLibs();
//
createmetaTable
LuaObjectmetaTableObj
=
state
->
GetGlobals().CreateTable(
"
MultiObjectMetaTable
"
);
metaTableObj.SetObject(
"
__index
"
,metaTableObj);
//
registerfunctorformultiobject
metaTableObj.RegisterObjectFunctor(
"
Print
"
,CMultiObject::Print);
//
getainstancesofCMultiObject
CMultiObjectobj1(
10
);
//
"clone"aobjectinlua,theluaobject(hereistable)hasobj1'sdata
LuaObjectobj1Obj
=
state
->
BoxPointer(
&
obj1);
//
setluaobject'smetatabletoMetaTableObj
obj1Obj.SetMetaTable(metaTableObj);
//
putluaobjecttoGlobalscope,thusitcanbeaccessedlater.
state
->
GetGlobals().SetObject(
"
obj1
"
,obj1Obj);
CMultiObjectobj2(
20
);
LuaObjectobj2Obj
=
state
->
BoxPointer(
&
obj2);
obj2Obj.SetMetaTable(metaTableObj);
state
->
GetGlobals().SetObject(
"
obj2
"
,obj2Obj);
//
nowcallPrintandPrint2
state
->
DoString(
"
obj1:Print();
"
);
state
->
DoString(
"
obj2:Print();
"
);
}
首先我們需要生成一個(gè)元表(metatable),將C++類的成員函數(shù)注冊(cè)到該元表中。然后依據(jù)CMultiObject的實(shí)例生成lua中與其對(duì)應(yīng)的對(duì)象(也是表),將該對(duì)象的metatable(也即該表的__object成員)設(shè)置為之前產(chǎn)生的元表。最后將新生成的lua對(duì)象放置到全局作用域中,這樣后面就可以直接引用這些對(duì)象。 我們可以做這樣的近似理解:每個(gè)實(shí)例的數(shù)據(jù)元素存放在與已對(duì)應(yīng)的lua table中,而類的成員函數(shù)則存放在metatable中(函子對(duì)象)。當(dāng)調(diào)用obj1obj:Print()時(shí),會(huì)先找到其metatable,然后在metatable中找Print()函數(shù)。 這樣便實(shí)現(xiàn)了類似C++中的類結(jié)構(gòu)。每個(gè)實(shí)例有自己的數(shù)據(jù),而所有實(shí)例共享一份方法列表。 另外一種方式是利用表的userdata來(lái)實(shí)現(xiàn),需要先創(chuàng)建一個(gè)lua表對(duì)象,然后將C++對(duì)象obj1設(shè)置為該表的userdata(也是設(shè)置其__object成員),再將該表對(duì)象的metatable設(shè)置為我們之前創(chuàng)建的元表。最后就可以用表明來(lái)調(diào)用Print函數(shù)。代碼如下:
LuaObjecttable1Obj
=
state
->
GetGlobals().CreateTable(
"
table1
"
);
table1Obj.SetLightUserData(
"
__object
"
,
&
obj1);
table1Obj.SetMetaTable(metaTableObj);
LuaObjecttable2Obj
=
state
->
GetGlobals().CreateTable(
"
table2
"
);
table2Obj.SetLightUserData(
"
__object
"
,
&
obj2);
table2Obj.SetMetaTable(metaTableObj);
state
->
DoString(
"
table1:Print()
"
);
state
->
DoString(
"
table2:Print()
"
);
注冊(cè)函子對(duì)象(RegisterObjectFunctor)這種方式的限制在于:要注冊(cè)的函數(shù)必須符合原型(
int
Callback(LuaState
*
state);
)。為了打破這種限制,LuaPlus提供了另外一種方式。
四、直接注冊(cè)函子對(duì)象
直接注冊(cè)函子對(duì)象(RegisterObjectDirect)和
RegisterDirect類似,不考慮函數(shù)原型,可以直接向元表注冊(cè)任意形式的函數(shù)
。 為CMultiObject添加新的成員函數(shù):
void
Print2(
int
num)
...
{
printf(
"
%d%d
/n"
,m_num,num);
}
調(diào)用RegisterObjectDirect方法:
metaTableObj.RegisterObjectDirect(
"
Print2
"
,(CMultiObject
*
)
0
,
&
CMultiObject::Print2);
第二個(gè)參數(shù)
(CMultiObject
*
)
0有點(diǎn)奇怪,這是模板參數(shù)的需要。
最后:
state
->
DoString(
"
obj1:Print2(5)
"
);
state
->
DoString(
"
obj2:Print2(15)
"
);
state
->
DoString(
"
table1:Print2(5)
"
);
state
->
DoString(
"
table2:Print2(15)
"
);
五、注銷回調(diào)
注銷回調(diào)是件簡(jiǎn)單的事情,調(diào)用SetNil("yourCallBack")即可,如:
gobj.SetNil(
"
Add
"
);
metaTableObj.SetNil(
"
Print2
"
);
好了,迄今為止最長(zhǎng)的一篇,看著像是LuaPlus文檔的翻譯(?),不過(guò)還是加入了一些自己的理解。文檔我看了下,琢磨了半天才明白。希望能快點(diǎn)將LuaPlus用起來(lái)。 資料: (1)
Lua5.1參考手冊(cè)
(2)
Lua入門(mén)wiki
(3)LuaPlus.html,源碼包中帶的。 <--!版權(quán)所有foruok,轉(zhuǎn)載注明出處!-->
LuaPlus學(xué)習(xí)(三)