Ogre原始碼淺析——外掛(Plugin)工作原理
Ogre引擎由多個模組組成,從不同角度來劃分可以得到不同的結果。從功能上看Ogre可大致分為資源管理、場景管理和渲染管理三大模組;而從可執行部分的組織方式看,Ogre引擎則是由多個dll動態連結庫組合而成的。
組成Ogre的各動態庫基本上可以分為以下幾部分:核心庫、渲染層、場景管理邏輯層。核心庫(OgreMain.dll,一般debug版會生成OgreMain_d.dll檔案)負責資源載入和管理,並根據實際情況選擇和載入對應的渲染層模組及場景管理層模組,同時排程協調各模組共同對場景進行渲染操作。渲染層主要是對不同的底層渲染引擎進行封裝,使不同的底層渲染引擎如:DirectX、OpenGL、OpenGLES等能夠支援相同的渲染介面,以實現核心層用統一的方式對不同底層渲染引擎的呼叫。這種技術有點類似《設計模式》中提到的Adapter模式。為了支援不同的底層渲染引擎,Ogre要有針對性地生成不同的動態連結庫如:RenderSystem_Direct3D9.dll、RenderSystem_Direct3D11.dll、RenderSystem_GL.dll、 RenderSystem_GLES.dll等。場景管理層則主要根據實際需要,採用不同的演算法來實現對場景物件的快速裁剪,比如若要用八叉樹對場景進行管理時一般要生成並呼叫Plugin_OctreeSceneManager.dll庫,而要用BSP演算法則需要生成並呼叫Plugin_BSPSceneManager.dll庫等。
以上所說的這些動態庫要能協調一致地工作,需要首先解決以下兩個問題:1. 如果不考慮跨平臺並假設是在Windows平臺上執行,如何實現各動態庫與核心庫的銜接?Ogre是用面嚮物件的技術開發的,Windows平臺只提供了動態載入動態連結庫及呼叫其中庫函式的相關技術,並沒有現成的對動態載入的動態庫函式中物件的訪問方法。2. 如果考慮跨平臺的問題,那麼該如何對動態庫的動態載入過程作進一步的抽象?(另外,如果要考慮將渲染引擎引入到IOS平臺上,那麼除了要對載入過程進行適當抽象外,還要考慮到IOS並不支援動態連結庫的實際情況,而只能採用靜態庫的使用方法。此問題不在本文的討論範圍之內)
對於以上問題,Ogre採用了稱之為外掛(Plugin)的技術來加以解決,這種方法本身比較巧妙,而且對模組化程式設計有一定的借鑑意義,所以值得詳細分析一下。
首先Ogre對要載入的動態庫作了一個抽象,用DynLib類來表示,一個動態庫就是一個DynLib類物件。同時Ogre又定義了一個DynLibManager類,用來管理所有載入的DynLib類物件,它負責根據動態庫檔名對相應的庫進行載入,並儲存載入後的DynLib物件指標。
此處採用的是Ogre1.8的相關程式碼。
void Root::loadPlugin(const String& pluginName) { #if OGRE_PLATFORM != OGRE_PLATFORM_NACL // Load plugin library DynLib* lib = DynLibManager::getSingleton().load( pluginName ); // Store for later unload // Check for existence, because if called 2+ times DynLibManager returns existing entry<br> if (std::find(mPluginLibs.begin(), mPluginLibs.end(), lib) == mPluginLibs.end()) { mPluginLibs.push_back(lib); // Call startup function DLL_START_PLUGIN pFunc = (DLL_START_PLUGIN)lib->getSymbol("dllStartPlugin"); if (!pFunc) OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, "Cannot find symbol dllStartPlugin in library " + pluginName, "Root::loadPlugin"); // This must call installPlugin pFunc(); } #endif }
以上程式碼中,DynLibManager::getSingleton().load( pluginName )的載入過程會最終呼叫到DynLib的Load函式,此Load函式的核心部分是:
mInst = (DYNLIB_HANDLE)DYNLIB_LOAD( name.c_str() );
其中DYNLIB_LOAD是預先定義好的一個巨集,其定義如下:
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
# define DYNLIB_HANDLE hInstance
# define DYNLIB_LOAD( a ) LoadLibraryEx( a, NULL, LOAD_WITH_ALTERED_SEARCH_PATH )
# define DYNLIB_GETSYM( a, b ) GetProcAddress( a, b )
# define DYNLIB_UNLOAD( a ) !FreeLibrary( a )
struct HINSTANCE__;
typedef struct HINSTANCE__* hInstance;
#elif OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_ANDROID || OGRE_PLATFORM == OGRE_PLATFORM_NACL
# define DYNLIB_HANDLE void*
# define DYNLIB_LOAD( a ) dlopen( a, RTLD_LAZY | RTLD_GLOBAL)
# define DYNLIB_GETSYM( a, b ) dlsym( a, b )
# define DYNLIB_UNLOAD( a ) dlclose( a )
#elif OGRE_PLATFORM == OGRE_PLATFORM_APPLE || OGRE_PLATFORM == OGRE_PLATFORM_APPLE_IOS
# define DYNLIB_HANDLE void*
# define DYNLIB_LOAD( a ) mac_loadDylib( a )
# define DYNLIB_GETSYM( a, b ) dlsym( a, b )
# define DYNLIB_UNLOAD( a ) dlclose( a )
#endif
通過這個巨集定義,實現了對不同平臺的動態庫載入的抽象,解決了之前提出的第二個問題。
Ogre會在每個要被動態載入的dll庫物件中宣告一個名為“dllStartPlugin”的函式,以RenderSystem_Direct3D9.dll為例:
#ifndef OGRE_STATIC_LIB
namespace Ogre
{
D3D9Plugin* plugin;
extern "C" void _OgreD3D9Export dllStartPlugin(void) throw()
{
plugin = OGRE_NEW D3D9Plugin();
Root::getSingleton().installPlugin(plugin);
}
extern "C" void _OgreD3D9Export dllStopPlugin(void)
{
Root::getSingleton().uninstallPlugin(plugin);
OGRE_DELETE plugin;
}
}
#endif
當載入完RenderSystem_Direct3D9.dll後,緊接著就呼叫"dllStartPlugin"函式。而在此函式中會生成一個D3D9Plugin的物件,並通過Root::installPlugin介面將此物件指標回傳給核心庫的Root物件,這樣一來,核心庫就可以通過這個指標來訪問動態載入的動態庫中的物件了,之前提出的第一個問題得到解決。
在早期的Ogre版本中,這種技術只用來處理三大模組的分離。到了後期,Ogre引入了SampleBrowser的概念,同時將各Sampler也編譯成相應的dll檔案,這樣一來,每個Sampler的載入和執行也以Plugin外掛技術為基礎了。不論是早期的模組分離,還是後期的Sampler演示,都會用一個.cfg配製檔案來描述要載入的Plugin外掛,引擎會先讀取相應的.cfg檔案,再根據此配製檔案的描述來搜尋和載入所需析Plugin外掛,這就使得模組的載入變得更加指令碼化了,從而更方便,更靈活了。
Ogre的Plugin原理的啟示:
當需要將程式進行模組化處理時,Ogre的這種Plugin技術是頗值得借鑑的。應用它可以在採用面向物件的程式設計技術並進行動態載入動態庫的時候,相當方便地對程式碼邏輯進行模組化設計。在此基礎上如果再引入配製檔案以實現自動載入,則會使程式的使用變得更為靈活和方便。
轉載自:https://www.cnblogs.com/yzwalkman/archive/2012/12/20/2826687.html