xlua中lua對象到c#對象的轉型

xlua中lua對象到c#對象的轉型

lua中的類型
基礎類型

define LUA_TNIL 0

define LUA_TBOOLEAN 1

define LUA_TLIGHTUSERDATA 2

define LUA_TNUMBER 3

define LUA_TSTRING 4

define LUA_TTABLE 5

define LUA_TFUNCTION 6

define LUA_TUSERDATA 7

define LUA_TTHREAD 8

變體(或者說子類型)

/*
** tags for Tagged Values have the following use of bits:
* bits 0-3: actual tag (a LUA_T value)
** bits 4-5: variant bits
** bit 6: whether value is collectable
*/

/*
** LUA_TFUNCTION variants:
** 0 - Lua function
** 1 - light C function
** 2 - regular C function (closure)
*/

/ Variant tags for functions /

define LUA_TLCL (LUA_TFUNCTION | (0 << 4)) / Lua closure /

define LUA_TLCF (LUA_TFUNCTION | (1 << 4)) / light C function /

define LUA_TCCL (LUA_TFUNCTION | (2 << 4)) / C closure /

/ Variant tags for strings /

define LUA_TSHRSTR (LUA_TSTRING | (0 << 4)) / short strings /

define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) / long strings /

/ Variant tags for numbers /

define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4)) / float numbers /

define LUA_TNUMINT (LUA_TNUMBER | (1 << 4)) / integer numbers /

/ Bit mark for collectable types /

define BIT_ISCOLLECTABLE (1 << 6)

lua中的對象都是用TValue來描述的,TValue中的tt_成員變量代表着這個TValue的類型。關於類型的具體定義,上面貼的代碼中的註釋中已經講的比較清楚了。
一個lua對象的類型是由一個7位的bits描述的。比如一個整數,這個對象的類型就是0011000(24)表示這個對象是數字類型中的整形,是一個不可回收對象。

C#如何獲取lua對象
和c語言和lua交互其實沒啥本質區別,就是通過lua提供的c函數操作lua棧,直接從棧中取就可以了。區別在於如何把取到的值轉換爲c#認識的值。

如何在C#端描述這些類型
簡介
lua的類型中boolean、string、number這幾個類型是clr所認識的類型,所以clr就可以直接把這些類型拿過來用。具體就是直接調用Lua提供的lua_tonumber之類的c接口。
lightUserData、table、function、userData、thread是C#不認識的類,需要通過某種標識(lua自帶的reference系統)來表示。

boolean、string、number類
這三個類上面已經說過了,直接用提供的接口轉就可以,下面寫幾個需要注意的點:

string雖然也是一個引用類型,但是clr在拿到這個string的指針時,還需要將這個string的數據直接複製進clr中才算轉型結束(xlua也已經封裝好了,不用我們自己去複製)。
大部分類型轉型失敗的時候都不會報錯,而是會返回一個默認值。就拿將一個lua對象轉爲int來說,最終是通過lua_tointegerx函數調用的,當lua對象不是number類型時,返回0:
LUA_API lua_Integer lua_tointegerx (lua_State L, int idx, int pisnum) {
lua_Integer res;
const TValue *o = index2addr(L, idx);
int isnum = tointeger(o, &res);
if (!isnum)

res = 0;  /* call to 'tointeger' may change 'n' even if it fails */

if (pisnum) *pisnum = isnum;
return res;
}
當一個number類型是浮點數時,轉型整數不會進行取整操作,而是會直接返回0。因爲lua默認對float轉int的操作模式LUA_FLOORN2I是0,代表碰見float轉int時返回0。
/*
** try to convert a value to an integer, rounding according to 'mode':
** mode == 0: accepts only integral values
** mode == 1: takes the floor of the number
** mode == 2: takes the ceil of the number
*/
int luaV_tointeger (const TValue obj, lua_Integer p, int mode) {
TValue v;
again:
if (ttisfloat(obj)) {

lua_Number n = fltvalue(obj);
lua_Number f = l_floor(n);
if (n != f) {  /* not an integral value? */
  if (mode == 0) return 0;  /* fails if mode demands integral value */
  else if (mode > 1)  /* needs ceil? */
    f += 1;  /* convert floor to ceil (remember: n != f) */
}
return lua_numbertointeger(f, p);

}
else if (ttisinteger(obj)) {

*p = ivalue(obj);
return 1;

}
else if (cvt2num(obj) &&

        luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) {
obj = &v;
goto again;  /* convert result from 'luaO_str2num' to an integer */

}
return 0; / conversion failed /
}
userData
userData主要是lua對c#對象的引用,這裏只簡單說一下。
代表c#對象的userData主要分兩種。

把c#對象存在ObjectTranslator中,用下標作爲引用(類似於lua中的reference)。
經過GC優化的結構體和枚舉,不存在ObjectTranslator中,而是把所有內容都打包到userdata中一起傳入lua中。比如一個Vector3,那麼xlua會把這個Vector3的x、y、z作爲3個連續的float一起打包到userdata中。這樣就避免了c#層的裝箱、拆箱和gc操作。
對table與function的引用簡介
這兩個類型都是通過lua的reference系統來讓c#持有對lua對象的引用。

lua reference系統
c#就是通過這個系統來持有不認識的lua對象的。
一共就兩個接口:

luaL_ref:把棧頂元素加入一個lua的表中,並返回下標。
luaL_unref:把一個下標所代表元素從表中刪除。
這樣就可以用一個整數來讓lua外的環境持有這個lua對象。
具體可以看下官方說明lua References

luaBase類
所有lua對象在c#中的基類,在初始化時通過luaL_ref生成lua對象的引用,在析構時通過luaL_unref移除引用。

對table的引用
LuaTable
一般情況下table在C#中被包裝爲LuaTable類,沒啥特別的,只是在LuaBase的基礎上增加了幾個常用的函數。比如Get、Set之類的,讓開發者可以避開一些不直觀的棧操作。

Array、List、Dictionary
這幾個都差不多。都是把table中的key和value全部拿出來,組成Array或Dictionaray。

接口、其他類
這兩種轉型是嘗試把這個table看作對應的接口或類。
比如將一個table轉爲IEnumberator就是把table轉爲SystemCollectionsIEnumeratorBridge類(繼承了LuaBase、實現了IEnumerator的類,由Xlua生成),這個類實現了MoveNext和Reset。實現方法就是調用一下table中對應名稱的函數。

對function的引用
lua函數在c#中有兩種表示:

LuaFunction
LuaFunction和luaTable差不多,也是在LuaBase的基礎上增加了幾個常用函數,Call、Action之類的。

DelegateBridge
爲什麼已經有LuaFunction還要一個DelegateBridge類?
因爲我們在c#中拿到一個lua函數時,大多數時候是要作爲一個委託來時用的。DelegateBridge就是用來化簡這個轉型操作的。
DelegateBridge的功能就是在持有lua函數引用的同時,將這個函數包裝成各種各樣的委託,讓整個轉型過程對開發人員無感知。
下面是一個不使用DelegateBridge,自己轉型的例子,比較繁瑣:

//將一個LuaFunction作爲一個Action使用
//其實LuaFunction.Cast就是幹這個的,這裏只是用簡單的方式表達出來
public static Action LuaFunctionToActionInt(XLua.LuaFunction luaFunction)
{

//由於luaFunction已經提供了Call操作封裝了函數調用的各種棧操作,所以我們這裏只需要用一個Action<int>把這個操作包裝起來即可
return (x) =>
{
    luaFunction.Call(x);
};

}

public static void Test()
{

XLua.LuaEnv luaEnv = new XLua.LuaEnv();
object[] rets = luaEnv.DoString("return function(x) CS.UnityEngine.Debug.LogError(\"print x: \"..x) end");
var luaFunction = (XLua.LuaFunction)rets[0];
Action<int> actionInt = LuaFunctionToActionInt(luaFunction);
actionInt(10);

}
DelegateBridge重要成員

xlua在將lua函數轉型的時候做了什麼

Tips
通過ObjectTranslator.getDelegateUsingGeneric生成委託時,會對返回值和參數進行不爲值類型的約束。因爲值類型在il2cpp下會有jit異常。這也是爲什麼我們發現有的委託類型不用註冊也可以使用,但是有的就不行。
在編輯器模式下,沒有進行代碼生成時,會通過Emit直接生成一個XLuaGenDelegateImplx類,內容和通過代碼生成後的DelegateBridge一樣,而不是全部通過反射來進行轉型。讓沒有進行代碼生成時的環境和真機環境更接近。
DelegateBridge一般不會被直接引用,而是被bindto中的委託生成的閉包引用和被delegate_bridges作爲弱引用持有。當一個DelegateBridge的bindto中的委託沒有被任何對象引用時,這個DelegateBridge就會在下次gc時被gc掉。
其他
這裏主要寫了常用lua類型轉型的簡介和一些關鍵點。可能不夠全面和細節。
如果有什麼錯誤或者問題可以在下面留言。

原文地址https://www.cnblogs.com/blueberryzzz/p/13066922.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章