本来,打算写一下SampleBrowser范例的介绍的,但是觉得有必要在介绍OGRE代码之前先介绍一下OGRE插件机制的大致结构,毕竟OGRE里面插件机制设计的还是挺不错的,可以当做教科书般来学习,而且对以后代码的理解也有很多必要,所以在这里穿插一集,也让这个周末自己显得积极一些。好了,在一个简单的开场白之后我们进入主题,本章主要介绍的内容主要如下:
- 动态库
动态库的一些基本知识 - OGRE插件
OGRE插件的架构,实现机制
动态库简介
动态库&静态库
无论是动态库还是静态库,都是用来存放一些公共函数的库文件,在代码的重用性上提供了很大的便捷,而两者之间的区别也很明显,下面给出一个简单的对比和个人经验
区别 | 优缺点 | 备注 | |
静态库 | 静态库则在编译期就将库内容打包到程序中,在程序运行时并不需要 | 独立性,兼容好 | 在最近移动设备,特别是Android的差异化和一些动态库数量的限制,静态库独立性,高兼容性的特点 |
动态库 | 运行时时调入,因此需要动态库的存在 | 灵活,体积小,重用度高 | 随着PC系统的标准化日益完善,目前的PC系统下静态库不如动态库常见 |
DLL&LIB
当我们编译完DLL后(Windows下),一般都会有一个同名字的lib文件,这个并不是静态库,我们可以理解这个对应DLL的配置文件,类似是一本书的目录,可以通过目录查询到这本书的某个函数或者某个类的位置。另外插一句,在Linux下只有so动态库,在脚本中-L就可以,没有lib文件照样玩得转,这个没有研究过。
使用方式 | 备注 | |
隐式链接 | 有三种方法:第一种是Dependence的方式,这适合有源码的方式,直接以来即可,不过这也是最弱的方式,会导致重复依赖等问题;第二种就是在VS工程里面添加lib文件,这个和makefile脚本的思路完全一样,第三种则是在头文件中添加#pragma comment(lib, “Lib.lib”) | 使用简单,需要头文件和lib文件 |
显示链接 | 步骤只有两部:加载DLL的Handle,通过LoadLibrary、dlopen函数,获取指定全局函数的函数指针pFun,通过GetProcAddress、dlsym函数。OGRE插件简介中很详细介绍 | 不需要lib文件和头文件,这正好满足了插件的特点 |
OGRE插件简介
一般情况下,插件加载主要有两种方式,一种是在Bin目录下面特定的后缀标识,在程序启动的时候自动加载,另外一种则是通过配置文件,将配置文件中指定的插件加载进来.而OGRE则是采用后者的方式.OGRE中涉及到插件的资源主要有两个plugins.cfg和samples_d.cfg,都放在bin目录下面,一个是OGRE工程所需要加载的插件,一个是范例所需要加载的SamplePlugin.
OGRE插件的加载和管理主要由三大块来完成
- Root
负责插件加载,管理的逻辑处理 - DynLibManager
插件动态库Handle的加载 - Plugin
插件基类外壳,所有插件的实体都继承自Plugin,达到实现的标准化
上图是OGRE插件加载的一个简单的逻辑过程,配合上面的流程图,简单说明一下:
- Root通过读取插件配置文件,加载具体指定的插件动态库,整个过程代码如下
1: void Root::loadPlugin(const String& pluginName)
2: {
3: // Load plugin library
4: DynLib* lib = DynLibManager::getSingleton().load( pluginName );
5: mPluginLibs.push_back(lib);
6: DLL_START_PLUGIN pFunc = (DLL_START_PLUGIN)lib->getSymbol("dllStartPlugin");
7: pFunc();
8: }
- 动态库文件的加载
1: #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
2: # define DYNLIB_HANDLE hInstance
3: # define DYNLIB_LOAD( a ) LoadLibraryEx( a, NULL, LOAD_WITH_ALTERED_SEARCH_PATH )
4:
5: DynLib* DynLibManager::load( const String& filename)
6: {
7: // 通过DynLib来load,最终获取该动态库的handle,DynLib只是一个封装的外壳
8: DynLib* pLib = OGRE_NEW DynLib(filename);
9: pLib->load();
10: mLibList[filename] = pLib;
11: return pLib;
12: }
13:
14: void DynLib::load()
15: {
16: //在Win下调用LoadLibraryEx来获取动态库handle
17: m_hInst = (DYNLIB_HANDLE)DYNLIB_LOAD( name.c_str() );
18: }
- OGRE中规定所有插件的动态库中都必须有一个全局函数命名为dllStartPlugin,此时已经获取DLL的handle,则通过该handle,获取该函数对应的函数指针
1: #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
2: # define DYNLIB_GETSYM( a, b ) GetProcAddress( a, b )
3: #endif
4: void* DynLib::getSymbol( const String& strName ) const throw()
5: {
6: // 最终调用GetProcAddress获取dllStartPlugin函数指针
7: return (void*)DYNLIB_GETSYM( m_hInst, strName.c_str() );
8: }
- 执行各自插件中的pFun函数,创建插件里面的具体类,实现具体功能,这里以RenderSystem_GL为例,创建OpenGL引擎后,调用installPlugin,将插件类的指针返回给Root进行维护管理
1: extern "C" void _OgreGLExport dllStartPlugin(void) throw()
2: {
3: plugin = new GLPlugin();
4: Root::getSingleton().installPlugin(plugin);
5:
6: }
- 执行Root的installPlugin函数,完成整改插件的加载过程
1: void Root::installPlugin(Plugin* plugin)
2: {
3: // 假如插件列表进行管理
4: mPlugins.push_back(plugin);
5: // 插件类执行函数,完成自己的特定功能
6: plugin->install();
7: }
如上,一个插件的加载过程基本结束,而Root类中分别由mPluginLibs和mPlugins来维护动态库handle和插件类的指针,通过插件来完成个性化的功能。
总结OGRE的插件方式,个人觉得做的非常优秀,虽然不复杂,但是非常的标准,条理清晰,而且难得可贵的是Plugin和DynLib、DynLibManager三个类,约束了插件的行为,使得整个插件都统一化,遵循标准的虚函数来实现自己,这样,随着类库的丰富和扩展,不会导致五花八门的插件泛滥。这种插件结构很成熟,也可以是我们以后开发程序时可以借鉴的架构方式。