由于luckyScript引擎接口使用上的不便,我為它實(shí)現(xiàn)了一個(gè)基于C++的封裝庫(kù),使用它可以比較方便地實(shí)現(xiàn):類的注冊(cè),任意C++函數(shù)的注冊(cè),調(diào)用腳本函數(shù),訪問腳本變量等比較核心的功能,雖然,用luckyScript引擎本身也可以做到上述這些,但我想你不會(huì)喜歡為每個(gè)主程序?qū)ο髮?shí)現(xiàn)一大堆回調(diào)處理函數(shù)的,那在需要提供給腳本使用的東西數(shù)量比較大的時(shí)候會(huì)是個(gè)讓人崩潰的工作量,所以,必須在luckyScript上再實(shí)現(xiàn)一層封裝簡(jiǎn)化這個(gè)過程,考慮到luckyScript只是一個(gè)無(wú)名小卒,沒有人會(huì)花時(shí)間去專門為它做那么個(gè)封裝的,所以只好由我自己來完成這個(gè)工作了,這個(gè)封裝庫(kù)的源碼會(huì)在發(fā)布luckyScript庫(kù)的時(shí)候附帶一起發(fā)布,下面,我詳細(xì)介紹下這個(gè)封裝庫(kù)最核心的幾個(gè)功能是如何實(shí)現(xiàn)的,雖然是基于luckyScript的封裝,但我想對(duì)于理解其他些比較流行的腳本(比如lua)的封裝庫(kù)也會(huì)是有用的。
一、任意C++函數(shù)的注冊(cè)
在luckyScript中,每個(gè)提供給腳本使用的主程序都必須有相同的原型,這在腳本中是為了可以方便的統(tǒng)一處理,但是,方便了開發(fā)者的東西通常就是給用戶提供了不方便,用戶得為每個(gè)在腳本中調(diào)用的主程序函數(shù)實(shí)現(xiàn)一個(gè)符合腳本規(guī)定原型的封裝,這會(huì)是個(gè)煩人的工作,為了避免它,我們必須用一層機(jī)制把這些細(xì)節(jié)隱藏起來,有了luckyScript中UserData的概念,我們很容易就能想到可以把注冊(cè)給腳本的函數(shù)跟真正調(diào)用的函數(shù)分離開來,事先把需要調(diào)用的函數(shù)指針當(dāng)作UserData傳給腳本,在腳本call的那個(gè)主程序函數(shù)里,我們?cè)侔堰@個(gè)函數(shù)指針取出來,之后就可以進(jìn)行我們真正所需要的函數(shù)調(diào)用了,這的確是個(gè)辦法,但馬上我們就會(huì)遇到另一個(gè)問題,UserData都是void指針類型的,我們?nèi)绾文茉谌〕鰜淼臅r(shí)候知道它是什么類型的函數(shù)指針呢?必須有個(gè)辦法能把這個(gè)函數(shù)指針的類型信息傳過來,于是,你會(huì)想到C++最強(qiáng)大的特性,模板,對(duì)了,可以把函數(shù)指針類型當(dāng)作模板參數(shù),這樣我們就可以把void指針轉(zhuǎn)換為正確的函數(shù)指針了,想到了這一層,我們就可以寫出給腳本調(diào)用的一個(gè)通用主函數(shù)的定義了:
1
template<typename Func>
2
struct Functor
3

{
4
static void invoke(RuntimeState* state)
5
{
6
7
}
8
};
9
Func就是我們需要的函數(shù)指針類型,在這個(gè)函數(shù)里面我們需要做些什么事情呢?恩,我們必須把函數(shù)指針數(shù)據(jù)取出來,轉(zhuǎn)換為正確的類型,然后再調(diào)用它,于是我們就遇到了第二個(gè)問題:Func只是一個(gè)類型的存貯,我們知道func是一個(gè)函數(shù)指針,但卻無(wú)法知道它具體是什么樣的一個(gè)函數(shù)指針,它有多少個(gè)參數(shù),有沒有返回值我們都不知道,因此,必須把函數(shù)的調(diào)用再往下移一層,利用模板參數(shù)的自動(dòng)匹配,通過傳遞Func參數(shù)讓程序自動(dòng)轉(zhuǎn)到我們有足夠信息來調(diào)用這個(gè)函數(shù)指針的地方去,請(qǐng)先看下面一段代碼:
1
template <typename RT>
2
int call(RT (*func)(), RuntimeState* state)
3

{
4
}
5
template <typename RT, typename P1>
6
int call(RT (*func)(P1), RuntimeState* state)
7

{
8
}
9
RT (*func)()代表沒有參數(shù)的函數(shù)指針類型,RT (*func)(P1)代表有一個(gè)參數(shù)的函數(shù)指針類型,調(diào)用這個(gè)call函數(shù),我們需要給一個(gè)函數(shù)指針參數(shù),根據(jù)函數(shù)指針的類型,編譯器會(huì)自動(dòng)尋找匹配的模板函數(shù),如果這個(gè)函數(shù)指針是沒有參數(shù)的,那么就會(huì)調(diào)用第一個(gè)call函數(shù),有一個(gè)參數(shù)則會(huì)調(diào)用第二個(gè),現(xiàn)在,我們上面的問題是不是已經(jīng)解決了?在Functor::invoke里面,我們能拿到正確的函數(shù)指針,這意味著我們可以調(diào)用call函數(shù)讓程序轉(zhuǎn)到能讓我們擁有更多函數(shù)信息的地方去,這樣就可以寫出Functor::invoke函數(shù)的代碼了
1
template<typename Func>
2
class Functor
3
{
4
public:
5
static void invoke(RuntimeState* state)
6
{
7
//函數(shù)指針buffer
8
unsigned char* buffer = (unsigned char*)lucky_popValueAsUserData(state);
9
10
//轉(zhuǎn)換為正確的函數(shù)指針類型并調(diào)用call
11
call((*(Func*)buffer),state);
12
}
13
};
然后在call里面,是不是就可以直接調(diào)用了呢?答案是還是不行,因?yàn)樵赾all里面,我們還是不知道函數(shù)是不是有返回值的,那個(gè)RT可能是void或具體的類型,因此,我們得把函數(shù)的調(diào)用進(jìn)一步下調(diào):
1
//有返回值
2
template<typename RT>
3
struct Caller
4
{
5
static int call(RT (*func)(), RuntimeState* state)
6
{
7
}
8
9
template <typename P1>
10
static int call(RT (*func)(P1), RuntimeState* state)
11
{
12
}
13
}
14
//沒有返回值
15
template<>
16
struct Caller<void>
17
{
18
static int call(void (*func)(), RuntimeState* state)
19
{
20
}
21
22
template <typename P1>
23
static int call(void (*func)(P1), RuntimeState* state)
24
{
25
}
26
}
在第二段代碼中的call函數(shù)里,我們這樣調(diào)用:
1
template <typename RT>
2
int call(RT (*func)(), RuntimeState* state)
3
{
4
return Caller<RT>::call(func, state);
5
}
6
7
8
template <typename RT, typename P1>
9
int call(RT (*func)(P1), RuntimeState* state)
10
{
11
return Caller<RT>::call(func, state);
12
}
這樣就能根據(jù)RT的類型進(jìn)一步轉(zhuǎn)到Caller<RT>::call或者Caller<void>::call上去了,ok,到這一步,我們已經(jīng)有足夠的信息來調(diào)用這個(gè)函數(shù)指針了,調(diào)用函數(shù),需要先取出參數(shù)信息,我們需要的參數(shù)都保存在棧上,luckyScript提供了lucky_popValueAs...樣的api來提供得到棧上元素值的功能,但這樣我們就會(huì)碰到一開頭的問題:調(diào)用這些API需要你事先知道棧頂元素的類型,可我們有的只是P1這樣的參數(shù)類型載體,確切的類型在這里是無(wú)法知道的,因此,有必要封裝參數(shù)取出的操作:
1
struct Param
2

{
3
static inline void get(TypeWrapper<void>, RuntimeState*)
4
{ }
5
static inline bool get(TypeWrapper<bool>, RuntimeState* state)
6
{
7
//bool對(duì)應(yīng)的是腳本中的int類型
8
return lucky_popValueAsInt(state) != 0; }
9
static inline char get(TypeWrapper<char>, RuntimeState* state)
10
{
11
//char也對(duì)應(yīng)int
12
return static_cast<char>(lucky_popValueAsInt(state));
13
}
14
}
上面的TypeWrapper只是為了區(qū)別參數(shù)類型而加進(jìn)去的,這里我只舉了void,bool,char,的取出操作作為示范,其實(shí)還有很多類型,就不一一列出了,根據(jù)不同的類型,我們調(diào)用不同的lucky_popValueAs..來取出參數(shù),與此類推,對(duì)于有返回值的函數(shù),把返回值傳給腳本的操作也是必須封裝的:
struct ReturnVal

{

static inline void set(RuntimeState* state, bool value)
{ lucky_setReturnValue(state,(int)value); }

static inline void set(RuntimeState* state, char value)
{ lucky_setReturnValue(state,(int)value); }

static inline void set(RuntimeState* state, unsigned char value)
{ lucky_setReturnValue(state,(int)value); }



..
』
這樣就可以用Param::get和ReturnVal::set來取得參數(shù)值及設(shè)置返回值了,為了讓事情能更簡(jiǎn)單點(diǎn),我們定義兩個(gè)宏:
#ifndef __defparam
#define __defparam(arg) P##arg p##arg = Param::get(TypeWrapper<P##arg>(),state);
#endif

#ifndef __return
#define __return(retVal) ReturnVal::set(state,retVal);
#endif
一切準(zhǔn)備就緒,可以進(jìn)行我們真正的函數(shù)調(diào)用操作了,下面用有一個(gè)參數(shù)的函數(shù)的調(diào)用代碼做例子,請(qǐng)注意看代碼的注釋
template<typename RT>
struct Caller

{
template <typename P1>
static int call(RT (*func)(P1), RuntimeState* state)

{
//測(cè)試棧上的元素是不是我們需要的類型
__argassert(1,-1);
//這句相當(dāng)于P1 p1 = Param::get(TypeWrapper<P1>(),state);
__defparam(1);
//調(diào)用函數(shù)
RT retVal = (*func)(p1);
//把返回值傳給腳本,這句是ReturnVal::set(state,retVal);
__return(retVal);

return 1;
}
}
這樣就完成了核心的函數(shù)調(diào)用,我們一開始的設(shè)想已經(jīng)實(shí)現(xiàn),現(xiàn)在該考慮提供給用戶注冊(cè)函數(shù)的接口的實(shí)現(xiàn)了,我們所需要用戶做的就是提供一個(gè)任意類型的函數(shù)指針跟一個(gè)在腳本中使用的函數(shù)名,之后我們就會(huì)將這個(gè)函數(shù)指針作為用戶數(shù)據(jù)傳進(jìn)腳本,然后再為這個(gè)函數(shù)類型注冊(cè)一個(gè)Functor<Func>::invoke函數(shù):
1
template<typename Func>
2
void registerFunction(Func func,const char* funcName)
3
{
4
//增加函數(shù)指針大小的UserData
5
unsigned char* buffer = (unsigned char*)lucky_addUserData(sizeof(func));
6
7
//復(fù)制函數(shù)指針數(shù)據(jù)
8
memcpy(buffer,&func,sizeof(func));
9
10
//得到返回類型名
11
std::string typeName = LuckySystem::ReturnType::name(func);
12
13
//注冊(cè)Functor<Func>::invoke
14
lucky_registerHostFunc(mRuntimeState,LuckySystem::Functor<Func>::invoke,funcName,typeName.c_str());
15
16
//清空跟主函數(shù)綁定的UserData
17
lucky_clearAddedUserData();
18
}
上面只是舉0到1個(gè)參數(shù)的函數(shù)指針的調(diào)用作為例子,通過定義更多的函數(shù)類型,我們就可以讓注冊(cè)函數(shù)的適用范圍進(jìn)一步擴(kuò)寬,在我所做的這個(gè)腳本封裝中,最多可以支持擁有7個(gè)參數(shù)的主函數(shù)的注冊(cè)。
二、任意類成員函數(shù)的注冊(cè)
原理跟注冊(cè)非成員函數(shù)是完全一樣的,唯一不同的是,我們需要把這個(gè)類成員函數(shù)所屬的對(duì)象的一個(gè)實(shí)例也當(dāng)作用戶數(shù)據(jù)傳給腳本,然后在腳本調(diào)用的那個(gè)函數(shù)里,我們?cè)侔堰@個(gè)實(shí)例跟函數(shù)指針一并取出來進(jìn)行調(diào)用。如果上面的內(nèi)容你都已經(jīng)認(rèn)真看了,那么理解下面的代碼是很容易的:
1
template<typename Callee,typename Func>
2
struct MemberFunctor
3
{
4
static void invoke(RuntimeState* state)
5
{
6
//取出函數(shù)指針buffer
7
unsigned char* funcBuffer = (unsigned char*)lucky_popValueAsUserData(state);
8
//取出類的實(shí)例buffer
9
unsigned char* calleeBuffer = (unsigned char*)lucky_popValueAsUserData(state);
10
11
//調(diào)用
12
call((*(Callee*)calleeBuffer),(*(Func*)funcBuffer),state);
13
}
14
};
在這個(gè)MemberFuncto::invoke里,除了取出成員函數(shù)指針外,我們還取出了這個(gè)成員函數(shù)所屬的類的一個(gè)實(shí)例,并把它們作為參數(shù)調(diào)用call函數(shù),在這里調(diào)用的call函數(shù)跟注冊(cè)非成員函數(shù)時(shí)的call重載相比,就是多了一個(gè)Callee& callee參數(shù)
template <typename Callee, typename RT>
int call(Callee& callee, RT (Callee::*func)(), RuntimeState* state)

{
return Caller<RT>::call(callee, func, state);
}
template <typename Callee, typename RT, typename P1>
int call(Callee& callee, RT (Callee::*func)(P1), RuntimeState* state)

{
return Caller<RT>::call(callee, func,state);
}
在Caller<RT>::call中,我們把類的實(shí)例跟成員函數(shù)指針連接起來進(jìn)行調(diào)用,以一個(gè)參數(shù)的函數(shù)指針調(diào)用代碼為例:
template <typename Callee, typename P1>
static int call(Callee& callee, RT (Callee::*func)(P1), RuntimeState* state)

{
__argassert(1,-1);

__defparam(1);

//調(diào)用成員函數(shù)
RT retVal = (callee.*func)(p1);

__return(retVal);

return 1;
}
三、類的注冊(cè)
要完整地將一個(gè)c++的類注冊(cè)給腳本使用,我們必須為這個(gè)類所包含的四個(gè)部分:構(gòu)造函數(shù) ,析構(gòu)函數(shù),屬性,方法提供主程序函數(shù)處理,luckyScript采用一套預(yù)定義的規(guī)范來命名這些主函數(shù),并在合適地方調(diào)用它們,同樣地,我們不可能為所有類都寫單獨(dú)的處理函數(shù),所以,必須為這四個(gè)部分做一個(gè)封裝,以使得能用一種統(tǒng)一的方式處理這個(gè)流程。
1.構(gòu)造/析構(gòu)函數(shù)的封裝
首先你需要了解的是在luckyScript中創(chuàng)建一個(gè)主程序?qū)ο髸r(shí),腳本會(huì)創(chuàng)建一塊跟這個(gè)主程序?qū)ο蟠笮∫粯拥膬?nèi)存空間,然后再調(diào)用跟這個(gè)主程序?qū)ο笸闹鞒绦蚝瘮?shù),并把這塊內(nèi)存壓棧傳給用戶,也就是說,構(gòu)建類的操作是由用戶完成的,用戶所需要做的就是用這塊內(nèi)存空間創(chuàng)建用戶所需要的對(duì)象,如果你對(duì)這些一點(diǎn)都不了解,我建議你可以先看看
這篇文章中關(guān)于注冊(cè)對(duì)象的部分。ok,那么,我們先來看看統(tǒng)一注冊(cè)給腳本的構(gòu)造函數(shù)是什么樣的:
1
template<typename Callee>
2
struct Creator
3
{
4
template<typename CONSTRUCTOR>
5
static void invoke(RuntimeState* state)
6
{
7
void* dataTrunk = lucky_popValueAsUserData(state);
8
9
CONSTRUCTOR::invoke<Callee>(state,dataTrunk);
10
}
11
};
Callee是對(duì)象的類型,在這個(gè)函數(shù)中,我們?nèi)〕瞿_本所為我們創(chuàng)建的那塊內(nèi)存空間,并把它傳給CONSTRUCTOR::invoke接著處理,這個(gè)CONSTRUCTOR指定了構(gòu)造函數(shù)的類型,但注意,它并不是構(gòu)造函數(shù)指針的類型,實(shí)際上,類的構(gòu)造函數(shù)是不允許你取址的,在上面的代碼中,它轉(zhuǎn)入的是下面這些代碼中的invoke函數(shù)里面,仍然以擁有0到1個(gè)參數(shù)的構(gòu)造函數(shù)處理為例:
1
template<typename P1>
2
struct Constructor<P1>
3
{
4
template<typename Callee>
5
static void invoke(RuntimeState* state,void* dataTrunk)
6
{
7
__argassert(1,-1);
8
9
__defparam(1);
10
11
new(dataTrunk) Callee(p1);
12
}
13
};
14
template<>
15
struct Constructor<void>
16
{
17
template<typename Callee>
18
static void invoke(RuntimeState* state,void* dataTrunk)
19
{
20
new(dataTrunk) Callee();
21
}
22
};
23
}
前面已經(jīng)多次出現(xiàn)這個(gè)__argassert宏,我并不打算具體向你解釋它的構(gòu)成,但你明白它是用來assert棧上的參數(shù)類型是否匹配的就行了,到這里,我們都應(yīng)該明白上面那個(gè)CONSTRUCTOR就是具體的struct Constructor,在這個(gè)結(jié)構(gòu)體的invoke方法里,我們?nèi)〕鰳?gòu)造函數(shù)的參數(shù),并調(diào)用構(gòu)造函數(shù),或許需要向你解釋下這個(gè)比較少用的new(dataTrunk)..,它所實(shí)現(xiàn)的效果就是編譯器在我們提供的那塊內(nèi)存中構(gòu)建對(duì)象,這樣構(gòu)建出來的對(duì)象是不能直接delete的,我們需要顯式調(diào)用對(duì)象的析構(gòu)函數(shù)。
那么接下來看看類的析構(gòu)函數(shù)的封裝,所幸的是析構(gòu)函數(shù)一定是沒有參數(shù)的,省去了很多麻煩:
1
template<typename Callee>
2
struct Destroytor
3
{
4
static void invoke(RuntimeState* state)
5
{
void* dataTrunk = lucky_popValueAsUserData(state);
6
Desconstructor<Callee>::invoke(state,dataTrunk);
7
}
8
};
這個(gè)Desconstructor<Callee>::invoke調(diào)用的就是下面的代碼:
1
template<typename Callee>
2
struct Desconstructor
3
{
4
static void invoke(RuntimeState* state,void* dataTrunk)
5
{
6
unsigned char* calleeBuffer = (unsigned char*)dataTrunk;
7
8
Callee* classObj = (Callee*)(calleeBuffer);
9
10
//顯式調(diào)用析構(gòu)函數(shù)
11
classObj->~Callee();
12
13
}
14
};
搞定了這些,我們就可以具體設(shè)計(jì)我們提供給用戶注冊(cè)類的接口了,至少,它已經(jīng)可以在腳本中創(chuàng)建與銷毀了,對(duì)吧?所以不需要有太多顧慮,前面已經(jīng)提到過,構(gòu)造函數(shù)是不能取址的,所以不能以用戶傳進(jìn)構(gòu)造函數(shù)指針的方式來完成類的注冊(cè),我們需要提供一系列的模板重載函數(shù),像下面這樣:
//沒有參數(shù)的構(gòu)造函數(shù)
template<typename C>
void registerClass(const char* className)


{
pushClassAndConstructor<C>(className,LuckySystem::Constructor<>());
}
//有一個(gè)參數(shù)的構(gòu)造函數(shù)
template<typename C,typename P1>
void registerClass(const char* className)


{
pushClassAndConstructor<C>(className,LuckySystem::Constructor<P1>());
}
根據(jù)Constructor<P1...>我們就具體指定了構(gòu)造函數(shù)的類型,接下來看這個(gè)pushClassAndConstructor做了什么事情:
template<typename Callee,typename CONSTRUCTOR>
void pushClassAndConstructor(const char* className,CONSTRUCTOR)

{
lucky_clearAddedUserData();

LuckySystem::ObjectName<Callee>::name(className);
LuckySystem::ObjectName<Callee*>::name(className);
LuckySystem::ObjectName<Callee&>::name(className);
LuckySystem::ObjectName<const Callee&>::name(className);
LuckySystem::ObjectName<Callee const>::name(className);
LuckySystem::ObjectName<const Callee*>::name(className);

//注冊(cè)與class同名的主程序構(gòu)造函數(shù)
lucky_registerGlobalHostFunc(LuckySystem::Creator<Callee>::invoke<CONSTRUCTOR>,className);

//下劃線+className,注冊(cè)主程序析構(gòu)函數(shù)
std::string destroyFuncName = "";
destroyFuncName += "_";
destroyFuncName = destroyFuncName + className;
lucky_registerGlobalHostFunc(LuckySystem::Destroytor<Callee>::invoke,destroyFuncName.c_str());

size_t size = sizeof(Callee);
lucky_registerHostClass(className,size);
}
CONSTRUCTOR指定了構(gòu)造函數(shù)的類型,所以我們注冊(cè)給腳本的構(gòu)造函數(shù)處理主函數(shù)是:Creator<Callee>::invoke<CONSTRUCTOR>,而析構(gòu)函數(shù)為Destroytor<Callee>::invoke,上面的Object<Callee>::name把這一class類型的名字保存了下來,如果沒有為name方法指定參數(shù),那么會(huì)返回callee這個(gè)類型的class的名字(假如之前有存的話),為了限制篇幅,我并不打算貼出它的代碼
2.往注冊(cè)的類添加成員方法
當(dāng)我們?cè)趌uckyScript中調(diào)用一個(gè)主程序?qū)ο蟮姆椒〞r(shí),腳本會(huì)把這個(gè)主程序?qū)ο髩簵鹘o用戶,并按預(yù)定義的規(guī)則得到處理主函數(shù)名,然后再調(diào)用它交給用戶處理,所以我們完全可以按照注冊(cè)類成員函數(shù)的方法,事先把類成員函數(shù)指針當(dāng)作用戶數(shù)據(jù)壓棧,在腳本調(diào)用主程序函數(shù)時(shí),一并取出來調(diào)用:
1
template<typename Callee,typename Func>
2
void addMemFunc(Func func,const char* funcName)
3
{
4
//增加函數(shù)指針大小的用戶數(shù)據(jù)
5
unsigned char* funcBuffer = (unsigned char*)lucky_addUserData(sizeof(func));
6
//copy函數(shù)指針數(shù)據(jù)
7
memcpy(funcBuffer,&func,sizeof(Func));
8
9
//得到Callee類型的class的名字
10
const char* className = LuckySystem::ObjectName<Callee>::name();
11
12
//成員函數(shù)處理主函數(shù)name:類型名 + 下劃線 + 成員函數(shù)名
13
std::string realFuncName = className;
14
realFuncName = realFuncName + "_" + funcName;
15
lucky_registerGlobalHostFunc(LuckySystem::MemberFunctor<Callee,Func>::invoke,realFuncName.c_str());
16
17
lucky_clearAddedUserData();
18
19
std::string typeName = LuckySystem::ReturnType::name(func);
20
21
lucky_addHostMemberFunc(className,funcName,typeName.c_str());
22
}
注意這個(gè)MemberFunctor<Callee,Func>::invoke就是注冊(cè)類成員函數(shù)時(shí)使用的那個(gè)主函數(shù)封裝,前面已對(duì)其做過介紹,在這個(gè)函數(shù)里面,我們?nèi)〕鲱惖膶?shí)例,再取出類成員函數(shù)的指針,合并調(diào)用
對(duì)類屬性變量的存取需要我們提供兩個(gè)新的主函數(shù)封裝,同樣地,luckyScript在遇到主程序?qū)ο蟮拇嫒r(shí)也會(huì)調(diào)用用戶提供的主函數(shù)進(jìn)行處理,并把這個(gè)主程序?qū)ο蟾也僮鲾?shù)壓棧傳給用戶,需要特別說明的是:由于賦值操作符有很多種,當(dāng)遇到主程序成員變量的賦值時(shí),luckyScript除了會(huì)把主程序?qū)ο蠛陀也僮鲾?shù)壓棧傳給用戶外,還會(huì)把操作符也壓棧傳給用戶以給用戶足夠的信息進(jìn)行賦值,當(dāng)然這是在這個(gè)成員變量不是對(duì)象類型的時(shí)候,假如這個(gè)成員變量本身也是在腳本中注冊(cè)過的對(duì)象,那么會(huì)調(diào)用操作符重載主函數(shù)進(jìn)行處理
1
//成員變量為右操作數(shù)
2
template<typename Callee,typename ValueType>
3
struct MemberValueGettor
4
{
5
static void invoke(RuntimeState* state)
6
{
7
//取出成員變量指針數(shù)據(jù)
8
unsigned char* valBuffer = (unsigned char*)lucky_popValueAsUserData(state);
9
//取出類的實(shí)例
10
unsigned char* calleeBuffer = (unsigned char*)lucky_popValueAsUserData(state);
11
12
ValueType Callee::** val = (ValueType Callee::**)(valBuffer);
13
Callee* classObj = (Callee*)(calleeBuffer);
14
15
//返回成員變量值
16
__return(classObj->**(val));
17
}
18
};
19
20
//成員變量為左操作數(shù)
21
template<typename Callee,typename ValueType>
22
struct MemberValueSettor
23
{
24
typedef ValueType P1;
25
26
static void invoke(RuntimeState* state)
27
{
28
ValueType setVal;
29
//取出成員變量指針數(shù)據(jù)
30
unsigned char* valBuffer = (unsigned char*)lucky_popValueAsUserData(state);
31
//取出操作符
32
int opType = lucky_popValueAsInt(state);
33
if(opType != LUCKY_OP_TYPE_INC && opType != LUCKY_OP_TYPE_DEC)
34
{
35
__argassert(1,-1);
36
37
//取出右操作數(shù)的值
38
setVal = Param::get(TypeWrapper<ValueType>(),state);
39
}
40
//取出對(duì)象實(shí)例
41
unsigned char* calleeBuffer = (unsigned char*)lucky_popValueAsUserData(state);
42
43
44
//轉(zhuǎn)換類型
45
ValueType Callee::** val = (ValueType Callee::**)(valBuffer);
46
Callee* classObj = (Callee*)(calleeBuffer);
47
48
//根據(jù)不同的操作符進(jìn)行賦值處理
49
switch(opType)
50
{
51
case LUCKY_OP_TYPE_INC:
52
classObj->**(val) += 1;
53
break;
54
case LUCKY_OP_TYPE_DEC:
55
classObj->**(val) -= 1;
56
break;
57
//以下略過不貼

58
}
在為主程序增加成員變量的接口上,我們同樣要求用戶提供此成員變量的指針以及在腳本中使用的名字
1
template<typename Callee,typename ValueType>
2
void addMemVal(ValueType Callee::* val,const char* valName)
3
{
4
//增加成員變量指針大小的用戶數(shù)據(jù)
5
unsigned char* valBuffer = (unsigned char*)lucky_addUserData(sizeof(val));
6
//copy成員變量指針數(shù)據(jù)
7
memcpy(valBuffer,&val,sizeof(val));
8
9
//得到Callee類型的class的名字
10
const char* className = LuckySystem::ObjectName<Callee>::name();
11
12
//成員變量處理主函數(shù)命名規(guī)則:類型名 + 下劃線 + set/get + 下劃線 + 成員函數(shù)名
13
std::string realFuncName = className;
14
realFuncName = realFuncName + "_" + "get_" + valName;
15
lucky_registerGlobalHostFunc(LuckySystem::MemberValueGettor<Callee,ValueType>::invoke,realFuncName.c_str());
16
realFuncName = className;
17
realFuncName = realFuncName + "_" + "set_" + valName;
18
lucky_registerGlobalHostFunc(LuckySystem::MemberValueSettor<Callee,ValueType>::invoke,realFuncName.c_str());
19
20
lucky_clearAddedUserData();
21
22
//得到此成員變量的類型名,假如這個(gè)成員變量也是已經(jīng)注冊(cè)過的主程序?qū)ο箢愋停敲床粸榭?/span>
23
std::string typeName = LuckySystem::ObjectName<ValueType>::name();
24
25
lucky_addHostMemberVal(className,valName,typeName.c_str());
26
}
在這個(gè)函數(shù)里,我們把成員變量指針當(dāng)作用戶數(shù)據(jù)傳進(jìn)腳本,并為此成員變量的存取注冊(cè)兩個(gè)主程序處理函數(shù):MemberValueGetter<Callee,ValueType>::invoke,MemberValueSettor<Callee,ValueType>::invoke,當(dāng)腳本遇到這個(gè)成員變量的存取操作時(shí),會(huì)調(diào)用這兩個(gè)主函數(shù)進(jìn)行處理。
3.操作符重載
在遇到主程序?qū)ο蟮倪\(yùn)算時(shí),luckyScript會(huì)把操作符兩邊的value都?jí)簵#⒏鶕?jù)不同的操作符,調(diào)用不同的主函數(shù)進(jìn)行處理,我們所需要做的,就是在這些操作符重載處理主函數(shù)里,取出這兩個(gè)值,并進(jìn)行運(yùn)算操作:
template<typename Callee,typename ValueType>
struct OperatorOveridor

{
typedef ValueType P1;

static Callee* getClassObject(RuntimeState* state)

{
unsigned char* calleeBuffer = (unsigned char*)lucky_popValueAsUserData(state);
Callee* classObj = (Callee*)(calleeBuffer);

return classObj;
}
//自加操作符重載處理主函數(shù)
static void invokeInc(RuntimeState* state)

{
Callee* classObj = getClassObject(state);

//自加操作
(*classObj)++;
}
//賦值操作符重載處理主函數(shù)
static void invokeAssign(RuntimeState* state)

{
__argassert(1,-1);

//取出第二個(gè)操作數(shù)
__defparam(1);

Callee* classObj = getClassObject(state);

//進(jìn)行賦值操作
(*classObj) = p1;
}
}
在接口方面,我們僅需要用戶提供模板參數(shù)說明兩個(gè)操作數(shù)的類型,以賦值操作符為例:
template<typename Callee,typename ValueType>
void overideAssignOp()

{
const char* className = LuckySystem::ObjectName<Callee>::name();

std::string realFuncName = className;

//操作符重載處理主函數(shù)命名規(guī)則:類型名 + 下劃線 + "Overide" + 操作符英文符號(hào)
realFuncName = realFuncName + "_" + "Overide_" + "Assign";
lucky_registerGlobalHostFunc(LuckySystem::OperatorOveridor<Callee,ValueType>::invokeAssign,realFuncName.c_str());
}
注冊(cè)的主函數(shù)是上面的OperatorOveridor<Callee,ValueType>::invokeAssign,callee和valueType為操作符左右兩邊操作數(shù)類型
寫到這里,我已經(jīng)很累了,就不再對(duì)封裝調(diào)用腳本函數(shù)及訪問全局變量的操作進(jìn)行介紹了,之后源碼發(fā)布后可自行觀看,為了展示這個(gè)封裝庫(kù)的使用,我把OGRE的一些核心類跟接口注冊(cè)給腳本,并重寫了OGRE的兩個(gè)例子,本把想腳本源碼和截圖跟這篇文章放到一起,但考慮到這篇文章篇幅已經(jīng)很長(zhǎng),還是把它單獨(dú)放到另一篇文章吧
posted on 2009-04-18 17:37
清風(fēng) 閱讀(1662)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
LuckyScript