Posted on 2009-07-01 13:54
RichardHe 閱讀(1808)
評論(0) 編輯 收藏 引用 所屬分類:
OGRE
Ogre源碼剖析3–可擴展性&插件機制
Ogre是一個跨操作系統平臺的開源3D引擎,既支持DirectX,也支持使用OpenGL,支持可替換的場景管理算法(BSP, OCT)。為Ogre提供這些靈活可擴展性的基礎之一就是其面向插件的設計。
很多常用的軟件大都提供了插件接口,用以擴展應用程序設計者最初未想到的功能,比較常見的譬如PhotoShop的濾鏡,After Effect中的各種插件(最有名的比如shine),3dMax的插件譬如渲染器,魔獸世界的輔助插件等等。
通常,插件本身通常也需要實現主應用程序所需要的必要接口,從而使得插件可以被應用程序加載執行。此外,插件的實現也需要由主應用程序提供一些接口api,通過這些接口,插件可以對主應用程序的功能進行調用。
插件可以是動態鏈接庫(win32平臺上為DLL文件),也可以是以腳本的形式提供的,比如魔獸世界中的插件就是使用lua編寫的,插件也可能是某種應用程序自定義的文件,只要該應用程序提供了創建該類文件的方法并實現解析、執行功能即可。(不同的實現形式各有利弊,具體需要參考插件及應用程序所處的運行環境進行取舍)
采用插件的一個巨大的好處,以及很多應用程序中使用插件的主要目的就是,我們可以在不需要改動應用程序本身的情況下擴展應用程序的功能。
在Ogre中,插件被用來提供渲染子系統(RenderSystem),不同類別的圖形API被封裝在不同的渲染子系統的視線當中,Ogre默認提供了DX和OpenGL的實現,甚至,如果我們樂意,甚至可以只用繪點函數實現一套純軟件的渲染子系統提供給Ogre使用。以此為例,用簡單的形式來展示這種實現大概類似于下面這樣:
class RenderSystem
{
// … operations that a render system need to support
};
// In DX_RenderSystem.dll (in plugin dx rendersystem )
class DX_RenderSystem : public RenderSystem
{
// … implementation & override of the operations from RenderSystem using DirectX
};
// In GL_RenderSystem.dll (in plugin openGL rendersystem)
class GL_RenderSystem : public RenderSystem
{
// … implementation & override of the operations from RenderSystem using OpenGL
};
// …. We could implement any other rendersystem as we like
在引擎的內部(OgreMain),插件在初始化的時候,將一個渲染子系統的實例創建出來(可能是DX_RenderSystem,也可能是 GL_RenderSystem,也可能是其他),并將之掛接到OgreMain的Root對象當中。此后,OgreMain中的Root所有的渲染操作也就可以通過該接口訪問插件中創建的渲染子系統了。
本文接下來分析Ogre是如何實現插件的,以及插件是如何與OgreMain主引擎進行配合的。
事實上,想編寫一個插件是很簡單的,我們只需要約定幾個接口,插件將這些接口實現了,主應用程序通過某種機制(配置文件/遍歷某個目錄)加載插件,并查找接口在插件中是否被實現了,如果實現了,則調用之即可。
一個簡單的例子如下:
// in xx.dll
__declspec(dllexport) extern “C” // __declspec(dllexport)告知編譯器需要將該函數導出
// extern “C” 告知編譯器不要對函數做C++名字重整
const char* GetPluginName(void)
{
return “Test Plugin”;
}
// in application
HMODULE hInst = LoadLibrary(”xx.dll”);
if (!hInst) return;
typedef const char* (*GetPluginNameFunc)(void);
GetPluginNameFunc pFunc = GetProcAddress(hInst, “GetPluginName”);
if (!pFunc) return;
const char* szPluginName = pFunc();
// … do other things
事實上,在C語言里,我們就可以通過這種方式,約定一套需要實現的接口,交給插件實現即可。在這里,插件的功能都是在dll中實現的,我們需要加載 dll,保存句柄,并在插件卸載的時候釋放相應的資源。在C++中,我們顯然可以做的更好一點,比如將dll模塊相關的功能以及資源封裝起來:
class DynLib
{
public:
bool Load(const char* szPluginPath);
void* GetProcAddress(const char* szProcName);
private:
HMODULE m_hInst;
};
bool DynLib::Load(const char* szPluginPath)
{
m_hInst = LoadLibrary(szPluginPath);
return m_hInst != NULL;
}
void* DynLib::GetProcAddress(const char* szProcName)
{
return ::GetProcAddress(m_hInst, szProcName);
}
這樣一來,我們就有了一個DLL的簡單封裝。
但是一個dll里未必只能有一個插件,假若我們簡單的把插件接口設計為一套C導出函數,我們就不得不面對這種局限。經典的做法依舊是抽象,Ogre中定義了一個插件接口,每一個實現了插件接口的實例都代表了一個插件,如下:
class Plugin
{
public:
virtual const String& getName() = 0;
virtual void install() = 0;
virtual void initialize() = 0;
virtual void shutdown() = 0;
virtual void uninstall() = 0;
};
Ogre的每一個插件都需要實現以上接口。該接口提供的功能包括:
install 插件被加載時調用。
initialize 插件被初始化時調用。
shutdown 插件被反初始化時調用。
uninstall 插件被卸載時調用。
不過,單有這個接口,插件還是無法工作。插件中還需要提供兩個約定的C導出函數。這個稍后再說。
Ogre在Root對象中統一管理插件,因此Root對象提供了以下與插件相關的接口:
class Root
{
// …
void loadPlugin(const String& pluginName);
void unloadPlugin(const String& pluginName);
void installPlugin(Plugin* plugin);
void uninstallPlugin(Plugin* plugin);
// 以及對應的處理多個插件的接口
// …
};
當loadPlugin函數被調用時,Ogre將加載該插件的dll,從中查找并調用名為dllStartPlugin的導出函數。
對應的,當unloadPlugin函數被調用時,Ogre將從該dll中調用dllStopPlugin函數。并將該dll卸載。
每個插件在實現的時候,都必須提供上述的兩個C導出函數。其中,dllStartPlugin負責創建插件實例,并調用 Root::installPlugin,將創建好的插件指針傳遞給Root對象。在這里,dllStartPlugin實際可以創建多個插件實例,并依次調用Root::installPlugin,這樣我們就可以在一個DLL中包含多個插件了。在dllStopPlugin時,則需要調用 Root::uninstallPlugin,并將插件DLL中創建的plugin實例釋放掉。
一個典型的dllStartPlugin的實現像這樣:
class D3D9Plugin : public Plugin
{
//…
};
D3D9Plugin* plugin;
__declspec(dllexport) extern “C”
void dllStrartPlugin(void)
{
plugin = new D3D9Plugin();
Root::getSingleton().installPlugin(plugin);
}
__declspec(dllexport) extern “C”
void dllStopPlugin(void)
{
Root::getSingleton().uninstallPlugin(plugin);
delete plugin;
}
Root::getSingleton().installPlugin(plugin)會負責調用插件的install以及initialise操作,并將插件的指針存放起來,以備卸載時使用。
Plugin::install以及Plugin::initialise則分別負責創建OgreMain提供擴展功能接口的實例,以及將創建好的對象掛載到應用程序當中。
一個典型的Plugin的行為像這樣:
void BspSceneManagerPlugin::install()
{
mBspFactory = new BspSceneManagerFactory();
}
void BspSceneManagerPlugin::initialise()
{
Root::getSingleton().addSceneManagerFactory(mBspFactory);
}
其中,BspSceneManagerFactory是繼承自OgreMain中的SceneManagerFactory的派生類。
( class BspSceneManagerFactory : public SceneManagerFactory {…} )
通過將場景管理器工廠添加到Root對象當中,插件動態的將其功能添加到了OgreMain當中。
PS: SceneManagerFactory是一個用于創建SceneManager的工廠。SceneManager則是被用于場景管理的管理器。具體的 SceneManager也根據算法不同而不同,Ogre自帶提供了兩種類型的場景管理算法插件:Bsp以及Octree(實現了兩個對應的插件,分別是 Plugin_BSPSceneManager以及Plugin_OctreeSceneManager)。
通過上述方式,OgreMain核心引擎并不需要關心場景管理算法的具體實現,不需要關心渲染子系統的具體實現,不需要關心粒子系統的具體實現,等等。一切這些擴展性的功能都通過插件實現,并在加載時動態掛載到OgreMain當中,供OgreMain的引擎核心使用。
使用插件時幾個需要注意的地方:
1, 調用插件DLL方法創建的對象需要交由插件DLL釋放。(因為不同的鏈接單元可能具有不同的內存管理上下文環境,此處的new與彼處的new在語義上未必等同)
2, 調用插件DLL方法獲取的插件內對象的引用或指針,在插件DLL卸載之后就是無效的,必須保證不再使用。(比較容易引發問題的一個典型例子是從插件中傳遞回一個引用計數字符串,當DLL被卸載后,字符串內指向實際數據的指針已經無效,但是在該對象析構時,仍需要訪問該指針)