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被卸载后,字符串内指向实际数据的指针已经无效,但是在该对象析构时,仍需要访问该指针)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值