深入研究虛幻4反射系統實現原理(三)


前面兩篇文章介紹了虛幻引擎中對於反射的支持(如果沒讀過前兩篇文章,推薦你仔細看下,否則你可能不知道我在講什么),不過還差一點內容,就是這些信息是如何 加入到運行時的,講完這些那么這個系列也就算是基本結束了,下面進入正文。

信息注冊

虛幻引擎使用一系列靜態變量來注冊需要生成反射信息的函數,這個我們前面的文章已經比較詳細的講過。至於用生成的C++的代碼所帶來的好處,我前面翻譯的文章中也講過。下面我把它貼到這里。

用生成的C++代碼來存儲反射數據的一個最大好處就是,它可以保證跟二進制做到同步。你永遠也不會加載陳舊或者過時的反射數據,因為它是跟引擎的其它代碼同時編譯的,並且它會在程序啟動的時候使用C++表達式來計算成員偏移等,而不是通過針對特定平台/編譯器/優化的組合中進行逆向工程。UHT作為一個單獨的不使用任何生成頭文件的程序來構建,因此它也避免了雞生蛋、蛋生雞的問題,這個在虛幻3的腳本編譯器中一直被詬病。

UCLASS

對於類的反射支持,虛幻4分為兩步來做的。

  1. IMPLEMENT_CLASS() 這個宏用於在程序啟動時注冊這個類,包括生成UClass類以及注冊C++原生函數等操作。
  2. static FCompiledInDefer 創建一個靜態變量,用於向DeferredCompiledInRegistration這個靜態數組中添加注冊函數,來初始化默認的反射屬性。包括函數、成員變量、元數據等。

USTRUCT

對於結構體的支持,虛幻4引擎也是分為兩步來做的。

  1. static FCompiledInDeferStruct 存儲一個用於構建結構體的一個單例函數,用於在程序啟動的時候調用,讀者可以自行查看代碼就知道這個過程了。
  2. 還會創建一個靜態對象,這個對象在構造函數中會調用UScriptStruct::DeferCppStructOps,它用來向這個DeferredCppStructOps map 中注冊一個動態管理結構體構造、析構的一個類。

UENUM

枚舉比較簡單,只有一步。

  1. static FCompiledInDeferEnum 創建一個靜態變量,用於在程序啟動時存儲一個創建枚舉反射對象的一個單例函數。

啟動過程分析

上面我們講解了這個注冊信息的過程,而它們的執行是伴隨着當前模塊的加載而執行的,我們都知道靜態變量的初始化是先於Main函數執行的。下面我們簡單畫了一下虛幻編輯器的啟動流程,這樣我們就可以准確地看到整個注冊反射信息的過程了。

image

可以看到void ProcessNewlyLoadedUObjects()這個函數就是我們主要關注的函數,我們前面講到的注冊的信息,包括類、結構體以及枚舉類型的反射信息都會在這里進行注冊,它的代碼如下所示:

void ProcessNewlyLoadedUObjects()
{
	DECLARE_SCOPE_CYCLE_COUNTER(TEXT("ProcessNewlyLoadedUObjects"), STAT_ProcessNewlyLoadedUObjects, STATGROUP_ObjectVerbose);

#if WITH_HOT_RELOAD
	UClassGenerateCDODuplicatesForHotReload();
#endif
	UClassRegisterAllCompiledInClasses();

	while( AnyNewlyLoadedUObjects() )
	{
		UObjectProcessRegistrants();
		UObjectLoadAllCompiledInStructs();
		UObjectLoadAllCompiledInDefaultProperties();		
	}
#if WITH_HOT_RELOAD
	UClassReplaceHotReloadClasses();
#endif
}

下面我們對上面代碼做一個簡單的解釋,讀者也可以自行翻閱代碼來仔細查看它是怎么實現的。

  1. 代碼中WITH_HOT_RELOAD這個宏是用來處理C++代碼熱加載使用的。
  2. UClassRegisterAllCompiledInClasses()用來注冊所有要加載的類,這里面的所有類就是通過前面IMPLEMENT_CLASS()宏添加進來的。
  3. UObjectProcessRegistrants()用於處理自動注冊的對象,並把它們添加到ObjectArray中去,用於后期的檢索。
  4. UObjectLoadAllCompiledInStructs()用於注冊結構體和枚舉的反射信息。數組里面的數組是通過FCompiledInDeferStruct和FCompiledInDeferEnum創建的靜態對象注冊進去的。
  5. UObjectLoadAllCompiledInDefaultProperties()用於注冊類的反射信息並且創建一個默認對象(CDO)。

總結

到此為此,這個系列的3篇文章通過代表示例的方式向讀者展示了虛幻中反射系統的實現方式,原理其實很簡單,網上也有一些如何用C++支持反射類的方法,虛幻實現的是其中一種,因為有了UHT的幫助,所以好多臟活、累活都不用我們來做了,比如最笨的方法是我們自己實現一些宏用來注冊各種反射信息,但這樣效率還是比較低。而有的實現方式會從編譯器產生的調試信息入手,比如vs生成的pdb文件,或者clang生成的文件等,這樣做也可以,但是它有一個比較大的問題就是跨平台的支持不是特別友好。所以虛幻4中這一套UHT工具總體上來說還是很不錯的,如果后面有時間,希望給大家帶來對UHT工具的分析。對了,如果你有什么想對虛幻4引擎比較想了解的,也歡迎在下面留言,我也會挑大家比較感興趣的模塊來先做分析。接下來,我可能會主要把精力放在虛幻中藍圖的實現原理和基於物理的渲染具體實現上。敬請期待!


免責聲明!

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



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