Posted on 2009-07-01 13:54
RichardHe 閱讀(1808)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
OGRE
Ogre源碼剖析3–可擴(kuò)展性&插件機(jī)制
Ogre是一個(gè)跨操作系統(tǒng)平臺(tái)的開(kāi)源3D引擎,既支持DirectX,也支持使用OpenGL,支持可替換的場(chǎng)景管理算法(BSP, OCT)。為Ogre提供這些靈活可擴(kuò)展性的基礎(chǔ)之一就是其面向插件的設(shè)計(jì)。
很多常用的軟件大都提供了插件接口,用以擴(kuò)展應(yīng)用程序設(shè)計(jì)者最初未想到的功能,比較常見(jiàn)的譬如PhotoShop的濾鏡,After Effect中的各種插件(最有名的比如shine),3dMax的插件譬如渲染器,魔獸世界的輔助插件等等。
通常,插件本身通常也需要實(shí)現(xiàn)主應(yīng)用程序所需要的必要接口,從而使得插件可以被應(yīng)用程序加載執(zhí)行。此外,插件的實(shí)現(xiàn)也需要由主應(yīng)用程序提供一些接口api,通過(guò)這些接口,插件可以對(duì)主應(yīng)用程序的功能進(jìn)行調(diào)用。
插件可以是動(dòng)態(tài)鏈接庫(kù)(win32平臺(tái)上為DLL文件),也可以是以腳本的形式提供的,比如魔獸世界中的插件就是使用lua編寫(xiě)的,插件也可能是某種應(yīng)用程序自定義的文件,只要該應(yīng)用程序提供了創(chuàng)建該類文件的方法并實(shí)現(xiàn)解析、執(zhí)行功能即可。(不同的實(shí)現(xiàn)形式各有利弊,具體需要參考插件及應(yīng)用程序所處的運(yùn)行環(huán)境進(jìn)行取舍)
采用插件的一個(gè)巨大的好處,以及很多應(yīng)用程序中使用插件的主要目的就是,我們可以在不需要改動(dòng)應(yīng)用程序本身的情況下擴(kuò)展應(yīng)用程序的功能。
在Ogre中,插件被用來(lái)提供渲染子系統(tǒng)(RenderSystem),不同類別的圖形API被封裝在不同的渲染子系統(tǒng)的視線當(dāng)中,Ogre默認(rèn)提供了DX和OpenGL的實(shí)現(xiàn),甚至,如果我們樂(lè)意,甚至可以只用繪點(diǎn)函數(shù)實(shí)現(xiàn)一套純軟件的渲染子系統(tǒng)提供給Ogre使用。以此為例,用簡(jiǎn)單的形式來(lái)展示這種實(shí)現(xiàn)大概類似于下面這樣:
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
在引擎的內(nèi)部(OgreMain),插件在初始化的時(shí)候,將一個(gè)渲染子系統(tǒng)的實(shí)例創(chuàng)建出來(lái)(可能是DX_RenderSystem,也可能是 GL_RenderSystem,也可能是其他),并將之掛接到OgreMain的Root對(duì)象當(dāng)中。此后,OgreMain中的Root所有的渲染操作也就可以通過(guò)該接口訪問(wèn)插件中創(chuàng)建的渲染子系統(tǒng)了。
本文接下來(lái)分析Ogre是如何實(shí)現(xiàn)插件的,以及插件是如何與OgreMain主引擎進(jìn)行配合的。
事實(shí)上,想編寫(xiě)一個(gè)插件是很簡(jiǎn)單的,我們只需要約定幾個(gè)接口,插件將這些接口實(shí)現(xiàn)了,主應(yīng)用程序通過(guò)某種機(jī)制(配置文件/遍歷某個(gè)目錄)加載插件,并查找接口在插件中是否被實(shí)現(xiàn)了,如果實(shí)現(xiàn)了,則調(diào)用之即可。
一個(gè)簡(jiǎn)單的例子如下:
// in xx.dll
__declspec(dllexport) extern “C” // __declspec(dllexport)告知編譯器需要將該函數(shù)導(dǎo)出
// extern “C” 告知編譯器不要對(duì)函數(shù)做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
事實(shí)上,在C語(yǔ)言里,我們就可以通過(guò)這種方式,約定一套需要實(shí)現(xiàn)的接口,交給插件實(shí)現(xiàn)即可。在這里,插件的功能都是在dll中實(shí)現(xiàn)的,我們需要加載 dll,保存句柄,并在插件卸載的時(shí)候釋放相應(yīng)的資源。在C++中,我們顯然可以做的更好一點(diǎn),比如將dll模塊相關(guān)的功能以及資源封裝起來(lái):
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);
}
這樣一來(lái),我們就有了一個(gè)DLL的簡(jiǎn)單封裝。
但是一個(gè)dll里未必只能有一個(gè)插件,假若我們簡(jiǎn)單的把插件接口設(shè)計(jì)為一套C導(dǎo)出函數(shù),我們就不得不面對(duì)這種局限。經(jīng)典的做法依舊是抽象,Ogre中定義了一個(gè)插件接口,每一個(gè)實(shí)現(xiàn)了插件接口的實(shí)例都代表了一個(gè)插件,如下:
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的每一個(gè)插件都需要實(shí)現(xiàn)以上接口。該接口提供的功能包括:
install 插件被加載時(shí)調(diào)用。
initialize 插件被初始化時(shí)調(diào)用。
shutdown 插件被反初始化時(shí)調(diào)用。
uninstall 插件被卸載時(shí)調(diào)用。
不過(guò),單有這個(gè)接口,插件還是無(wú)法工作。插件中還需要提供兩個(gè)約定的C導(dǎo)出函數(shù)。這個(gè)稍后再說(shuō)。
Ogre在Root對(duì)象中統(tǒng)一管理插件,因此Root對(duì)象提供了以下與插件相關(guān)的接口:
class Root
{
// …
void loadPlugin(const String& pluginName);
void unloadPlugin(const String& pluginName);
void installPlugin(Plugin* plugin);
void uninstallPlugin(Plugin* plugin);
// 以及對(duì)應(yīng)的處理多個(gè)插件的接口
// …
};
當(dāng)loadPlugin函數(shù)被調(diào)用時(shí),Ogre將加載該插件的dll,從中查找并調(diào)用名為dllStartPlugin的導(dǎo)出函數(shù)。
對(duì)應(yīng)的,當(dāng)unloadPlugin函數(shù)被調(diào)用時(shí),Ogre將從該dll中調(diào)用dllStopPlugin函數(shù)。并將該dll卸載。
每個(gè)插件在實(shí)現(xiàn)的時(shí)候,都必須提供上述的兩個(gè)C導(dǎo)出函數(shù)。其中,dllStartPlugin負(fù)責(zé)創(chuàng)建插件實(shí)例,并調(diào)用 Root::installPlugin,將創(chuàng)建好的插件指針傳遞給Root對(duì)象。在這里,dllStartPlugin實(shí)際可以創(chuàng)建多個(gè)插件實(shí)例,并依次調(diào)用Root::installPlugin,這樣我們就可以在一個(gè)DLL中包含多個(gè)插件了。在dllStopPlugin時(shí),則需要調(diào)用 Root::uninstallPlugin,并將插件DLL中創(chuàng)建的plugin實(shí)例釋放掉。
一個(gè)典型的dllStartPlugin的實(shí)現(xiàn)像這樣:
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)會(huì)負(fù)責(zé)調(diào)用插件的install以及initialise操作,并將插件的指針存放起來(lái),以備卸載時(shí)使用。
Plugin::install以及Plugin::initialise則分別負(fù)責(zé)創(chuàng)建OgreMain提供擴(kuò)展功能接口的實(shí)例,以及將創(chuàng)建好的對(duì)象掛載到應(yīng)用程序當(dāng)中。
一個(gè)典型的Plugin的行為像這樣:
void BspSceneManagerPlugin::install()
{
mBspFactory = new BspSceneManagerFactory();
}
void BspSceneManagerPlugin::initialise()
{
Root::getSingleton().addSceneManagerFactory(mBspFactory);
}
其中,BspSceneManagerFactory是繼承自O(shè)greMain中的SceneManagerFactory的派生類。
( class BspSceneManagerFactory : public SceneManagerFactory {…} )
通過(guò)將場(chǎng)景管理器工廠添加到Root對(duì)象當(dāng)中,插件動(dòng)態(tài)的將其功能添加到了OgreMain當(dāng)中。
PS: SceneManagerFactory是一個(gè)用于創(chuàng)建SceneManager的工廠。SceneManager則是被用于場(chǎng)景管理的管理器。具體的 SceneManager也根據(jù)算法不同而不同,Ogre自帶提供了兩種類型的場(chǎng)景管理算法插件:Bsp以及Octree(實(shí)現(xiàn)了兩個(gè)對(duì)應(yīng)的插件,分別是 Plugin_BSPSceneManager以及Plugin_OctreeSceneManager)。
通過(guò)上述方式,OgreMain核心引擎并不需要關(guān)心場(chǎng)景管理算法的具體實(shí)現(xiàn),不需要關(guān)心渲染子系統(tǒng)的具體實(shí)現(xiàn),不需要關(guān)心粒子系統(tǒng)的具體實(shí)現(xiàn),等等。一切這些擴(kuò)展性的功能都通過(guò)插件實(shí)現(xiàn),并在加載時(shí)動(dòng)態(tài)掛載到OgreMain當(dāng)中,供OgreMain的引擎核心使用。
使用插件時(shí)幾個(gè)需要注意的地方:
1, 調(diào)用插件DLL方法創(chuàng)建的對(duì)象需要交由插件DLL釋放。(因?yàn)椴煌逆溄訂卧赡芫哂胁煌膬?nèi)存管理上下文環(huán)境,此處的new與彼處的new在語(yǔ)義上未必等同)
2, 調(diào)用插件DLL方法獲取的插件內(nèi)對(duì)象的引用或指針,在插件DLL卸載之后就是無(wú)效的,必須保證不再使用。(比較容易引發(fā)問(wèn)題的一個(gè)典型例子是從插件中傳遞回一個(gè)引用計(jì)數(shù)字符串,當(dāng)DLL被卸載后,字符串內(nèi)指向?qū)嶋H數(shù)據(jù)的指針已經(jīng)無(wú)效,但是在該對(duì)象析構(gòu)時(shí),仍需要訪問(wèn)該指針)