tolua#代碼簡要分析


簡介

tolua#是Unity靜態綁定lua的一個解決方案,它通過C#提供的反射信息分析代碼並生成包裝的類。它是一個用來簡化在C#中集成lua的插件,可以自動生成用於在lua中訪問Unity的綁定代碼,並把C#中的常量、變量、函數、屬性、類以及枚舉暴露給lua。它是從cstolua衍變而來。從它的名字可以看出,它是集成了原來的tolua代碼通過二次封裝寫了一個C#與tolua(c)的一個中間層。

All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections

基礎

要想了解tolua#是如何集成的我們需要對C#的一些特性有一些了解,比如了解C#與原生代碼交互的方式等。,我們假設讀者已經對lua和tolua++有一個比較熟悉,我們略過lua與c或者C++的交互方式,主要介紹一些C#的特性,來幫助我們接下來分析tolua#的集成原理。

C#特性

Attribute

Attribute 是一種可由用戶自由定義的修飾符(Modifier),可以用來修飾各種需要被修飾的目標。特性Attribute 的作用是添加元數據。元數據可以被工具支持,比如:編譯器用元數據來輔助編譯,調試器用元數據來調試程序。Unity以及tolua#中就會用Attribute來輔助做一些事情。

值類型與引用類型

只所以要提這兩個概念,是因為很好得理解這兩個概念有助於我們寫出比較高效的C#代碼。

我們知道,C#中的每一種類型要么是值類型,要么是引用類型。所以每個對象要么是值類型的實例,要么是引用類型的實例。

引用類型和值類型都繼承自System.Object類。不同的是,幾乎所有的引用類型都直接從System.Object繼承,而值類型則繼承其子類,即直接繼承System.ValueType。

作為所有類型的基類,System.Object提供了一組方法,這些方法在所有類型中都能找到,其中包含toString方法及clone等方法。

System.ValueType直接繼承System.Object,即System.ValueType本身是一個類類型,而不是值類型;System.ValueType沒有添加任何成員,但覆蓋了所繼承的一些方法,使其更適合於值類型。例如,ValueType重寫了Equals()方法,從而對值類型按照實例的值來比較,而不是引用地址來比較。

簡單了解了值類型與引用類型那么我們下面來看下C#中的裝箱和拆箱的概念。

裝箱和拆箱

裝箱和拆箱是值類型和引用類型之間相互轉換是要執行的操作。

1.    裝箱在值類型向引用類型轉換時發生

2.    拆箱在引用類型向值類型轉換時發生

1 object objValue = 4;
2 
3 int value = (int)objValue;

  

上面的兩行代碼會執行一次裝箱操作將整形數字常量4裝箱成引用類型object變量objValue;然后又執行一次拆箱操作,將存儲到堆上的引用變量objValue存儲到局部整形值類型變量value中。

同樣我們需要看下IL代碼:

 1 .locals init (
 2 
 3 [0] object objValue,
 4 
 5 [1] int32 'value'
 6 
 7 ) //上面IL聲明兩個局部變量object類型的objValue和int32類型的value變量
 8 
 9 IL_0000: nop
10 
11 IL_0001: ldc.i4.4 //將整型數字4壓入棧
12 
13 IL_0002: box [mscorlib]System.Int32 //執行IL box指令,在內存堆中申請System.Int32類型需要的堆空間
14 
15 IL_0007: stloc.0 //彈出堆棧上的變量,將它存儲到索引為0的局部變量中
16 
17 IL_0008: ldloc.0//將索引為0的局部變量(即objValue變量)壓入棧
18 
19 IL_0009: unbox.any [mscorlib]System.Int32 //執行IL 拆箱指令unbox.any 將引用類型object轉換成System.Int32類型
20 
21 IL_000e: stloc.1 //將棧上的數據存儲到索引為1的局部變量即value

 

拆箱操作的執行過程和裝箱操作過程正好相反,是將存儲在堆上的引用類型值轉換為值類型並給值類型變量。

C#調用原生代碼

因為tolua#底層庫是使用的tolua(c語言編寫),那么就需要通過C#來調用原生代碼,我們從LuaDll.cs中摘取一段代碼來演示如何從C#中調用原生代碼。

1 Public class LuaDll
2 
3 {
4 
5   [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
6 
7   public static extern void lua_close(IntPtr luaState);
8 
9 }

 

其中LUADLL對應的字符串就是tolua,在不同的平台上mono會去加載對應的tolua.dll或者tolua.so等文件並調用對應的函數。具體可以參考mono官方的教程。

tolua#集成

tolua#集成主要分為兩部分,一部分是運行時需要的代碼包括一些手寫的和自動生成的綁定代碼,另一部分是編輯器相關代碼,主要提供代碼生成、編譯lua文件等操作,具體就是Unity編輯器中提供的功能。用mono打開整個tolua#的工程,文件結構大體如下所示:

 

Runtime

    Source

        Generate 這個文件里面是生成的綁定代碼

        LuaConst.cs 這個文件是一些lua路徑等配置文件。

    ToLua

        BaseLua 一些基礎類型的綁定代碼

        Core 提供的一些核心功能,包括封裝的LuaFunction LuaTable LuaThread LuaState LuaEvent、調用tolua原生代碼等等。

        Examples 示例

        Misc 雜項,目前有LuaClient LuaCoroutine(協程) LuaLooper(用於tick) LuaResLoader(用於加載lua文件)

        Reflection 反射相關

Editor

    Editor

        Custom

            CustomSettings.cs 自定義配置文件,用於定義哪些類作為靜態類型、哪些類需要導出、哪些附加委托需要導出等。

    ToLua

        Editor

            Extend 擴展一些類的方法。

            ToLuaExport.cs 真正生成lua綁定的代碼

            ToLuaMenu.cs Lua菜單上功能對應的代碼

            ToLuaTree.cs 輔助樹結構

Generate All 流程

了解了tolua#的大致文件結構,下面我們來看下tolua#的Generate All 這個功能來分析下它的生成過程。生成綁定代碼主要放在ToLuaExport.cs里面,我們並不會對每一個函數進行細致的講解,如果有什么不了解的地方,可以直接看它的代碼。

GenLuaDelegates函數

生成委托綁定的代碼,它會從CustomSettings.customDelegateList里面取出所有自定義導出的委托列表,然后把CustomSettings.customTypeList里面的所有類型中的委托根據一定規則加入到list中,最后調用ToLuaExport.GenDelegates()方法來生成委托綁定的代碼,生成的代碼在DelegateFactory.cs文件中。

由於該函數比較簡單,我們這里不做展開,可以直接查看ToLuaExport.cs中的GenDelegates()並配合DelegateFactory.cs來查看。

GenerateClassWraps 函數

遍歷所有需要導出的類,然后調用ToLuaExport.Generate()方法來生成類的綁定代碼。

下面我們來看下ToLuaExport.Generate()方法,其基本流程如下所示:

從上面的流程圖我們可以看到,整個過程還是比較清楚的,如果這個類是枚舉類型,那么它會調用枚舉導出的接口,而如果這個類型是一個普通的類,那么它就會調用上圖所示的相應的流程將代碼導出。至於結構體類型,目前應該是只支持一些特定的結構體,需要在lua中對應一份實現(Assets\ToLua\Lua目錄中),當然它生成的代碼也有一些依賴於tolua#的核心運行時,我們前面簡單的講解了如何在編輯器中生成綁定代碼,接下來我們講一下它的核心運行時。

tolua#的核心運行時

tolua#的運行代碼包含SourceàGenerate下面的綁定代碼以及ToLuaàBaseType代碼以及Core下面的核心代碼。接下來我們着重講一下Core下面的幾個主要類。

LuaAttribute.cs

我們前面基礎知識部分已經講過,它在tolua#生成綁定代碼時做一些標示使用。

LuaBaseRef.cs

Lua中對象對應C#中對象的一個基類,主要作用是有一個reference指向lua里面的對象,引用計數判斷兩個對象是否相等等。

比如LuaFunction里面的reference是指向lua里面的一個閉包的,而LuaTable的reference是指向lua中的一個table的。

LuaDll.cs

這個類的主要作用就是實現了C#調用原生代碼的功能,其中的原理我們也在上面的基礎章節提到了如何在C#中調用原生代碼,這里我們就不展開去講了。

LuaState.cs

這里面是對真正的lua_State的封裝,包括初始化lua路徑,加載相應的lua文件,注冊我們前面生成的綁定代碼以及各種輔助函數。

ObjectTranslator.cs

接下來,我們着重說一下這個ObjectTranslator這個類,這個類代碼不多,它存在的主要意義就是給lua中對C#對象的交互提供了基礎,簡單來說就是C#中的對象在傳給lua時並不是直接把對象暴露給了lua,而是在這個OjbectTranslator里面注冊並返回一個索引(可以理解為windows編程中的句柄),並把這個索引包裝成一個userdata傳遞給lua,並且設置元表。具體可以查看tolua_pushnewudata代碼,如下所示:

 1 // tolua# 代碼
 2 
 3 static void PushUserData(IntPtr L, object o, int reference)
 4 
 5 {
 6 
 7   int index;
 8 
 9   ObjectTranslator translator = ObjectTranslator.Get(L);
10 
11   if (translator.Getudata(o, out index))
12 
13   {
14 
15   if (LuaDLL.tolua_pushudata(L, index))
16 
17   {
18 
19   return;
20 
21   }
22 
23   translator.Destroyudata(index);
24 
25   }
26 
27   index = translator.AddObject(o);
28 
29   LuaDLL.tolua_pushnewudata(L, reference, index);
30 
31 }
32 
33 // tolua++ 代碼 
34 
35 LUALIB_API void tolua_pushnewudata(lua_State *L, int metaRef, int index)
36 
37 {
38 
39     lua_getref(L, LUA_RIDX_UBOX);
40 
41     tolua_newudata(L, index);
42 
43     lua_getref(L, metaRef);
44 
45     lua_setmetatable(L, -2);
46 
47     lua_pushvalue(L, -1);
48 
49     lua_rawseti(L, -3, index);
50 
51     lua_remove(L, -2);    
52 
53 }

 

 

而在lua需要通過上面傳到lua里面的對象調用C#的方法時,它會調用ToLua.CheckObject或者ToLua.ToObject從ObjectTranslator獲取真正的C#對象。下面我們把ToLua.ToObject的代碼做個示例:

 1 public static object ToObject(IntPtr L, int stackPos)
 2 
 3 {
 4 
 5   int udata = LuaDLL.tolua_rawnetobj(L, stackPos);
 6 
 7   if (udata != -1)
 8 
 9   {
10 
11   ObjectTranslator translator = ObjectTranslator.Get(L);
12 
13   return translator.GetObject(udata);
14 
15   }
16 
17   return null;
18 
19 }

 

總結

通過對tolua#的簡單分析,我們對tolua#是怎么實現lua與Unity交互有了一個基礎的認識,如果想對tolua#有一個比較深入的了解,那么就需要我們仔細去研究代碼、示例以及用它來實際地去做些東西。

因為看的時間不是很多,所以理解上難免有錯誤,如果發現問題還請指正。前段時間也完整實現了一套虛幻4中的使用lua框架,希望有時間的話也能跟大家分享一下,當然如果你有興趣了解,也可以留言,這樣我會盡量抽時間來把實現的具體細節跟大家分享一下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM