Ogre(1.9)的插件原理

Ogre(1.9)的插件原理

研究目的

本人研究过部分cocos2dx游戏引擎等源码,它们都是揉在一团的,要修改引擎里面的一些代码,需要重新编译整套源码。这样,引擎的可扩展性就大大降低了。

Ogre这个引擎让我最吃惊的就是这个插件化的系统,渲染系统、场景管理系统 居然可以作为插件直接嵌入,还有如果要加入新的功能,也可以不需要重新编译整套源码,通过动态链接库的方式很方便地添加。

更让我惊喜的是,Ogre引入了SampleBrowser的概念,可以把一个一个游戏的场景作为一个一个的Sample,然后编译成相应的动态链接库,这样一来,每个Sample的加载和运行也以Plugin插件技术为基础了,即可以动态添加游戏场景 和 游戏逻辑,同时也是 热插拔的,即可以随时加载和卸载。这个做法,既具有脚本语言的强大灵活性,C++本身高效的运行效率也可以继续保留。

研究问题

  1. 如何实现热插拔的插件模块?
  2. 如何实现跨平台?
  3. 如何和插件模块进行交换?
  4. 插件工作的整个流程是?

解决问题

以 动态链接 的方式实现热插拔插件

插件加载的源码在,OgreDynLib.h 和 OgreDynLib.cpp,先从这里下手。

/** Resource holding data about a dynamic library.
    @remarks
        This class holds the data required to get symbols from
        libraries loaded at run-time (i.e. from DLL's for so's)
    @author
        Adrian Cearn„u ([email protected])
    @since
        27 January 2002
*/
class _OgreExport DynLib : public DynLibAlloc
{
protected:
    String mName;
    /// Gets the last loading error
    String dynlibError(void);
public:
    /** Default constructor - used by DynLibManager.
        @warning
            Do not call directly
    */
    DynLib( const String& name );

    /** Default destructor.
    */
    ~DynLib();

    /** Load the library
    */
    void load();
    /** Unload the library
    */
    void unload();
    /// Get the name of the library
    const String& getName(void) const { return mName; }

    /**
        Returns the address of the given symbol from the loaded library.
        @param
            strName The name of the symbol to search for
        @return
            If the function succeeds, the returned value is a handle to
            the symbol.
        @par
            If the function fails, the returned value is <b>NULL</b>.

    */
    void* getSymbol( const String& strName ) const throw();

protected:

    /// Handle to the loaded library.
    DYNLIB_HANDLE mInst;
};

看注释,我们可以清楚认识到,Ogre的插件库是使用动态链接库的方法做到的,而不是脚本。

构造函数,传入 Plugin的名字,load函数加载这个Plugin对应名字的动态链接库,unload函数卸载它,getSymbol通过符号获取动态链接库的函数。

用 宏 来解决跨平台问题

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#    define DYNLIB_HANDLE hInstance
#    define DYNLIB_LOAD( a ) LoadLibraryEx( a, NULL, 0 ) // we can not use LOAD_WITH_ALTERED_SEARCH_PATH with relative paths
#    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_WINRT
#    define DYNLIB_HANDLE hInstance
#    define DYNLIB_LOAD( a ) LoadPackagedLibrary( UTFString(a).asWStr_c_str(), 0 )
#    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 || OGRE_PLATFORM == OGRE_PLATFORM_FLASHCC
#    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 FRAMEWORK_LOAD( a ) mac_loadFramework( a )
#    define DYNLIB_GETSYM( a, b ) dlsym( a, b )
#    define DYNLIB_UNLOAD( a ) dlclose( a )

#endif

由于各大平台的动态链接库操作接口都是类似的,可以用 DYNLIB_HANDLE、 DYNLIB_LOAD、 DYNLIB_GETSYM 、 DYNLIB_UNLOAD 这四个宏来统一各平台的动态链接库的系统API。

使用跨动态链接库的单例Root,实现交互

加载插件后,如何与插件的交互 是 一个永恒的话题。Ogre的解决方法真是十分机智。

SamplePlugin* sp;
Sample* s;

extern "C" _OgreSampleExport void dllStartPlugin()
{
    s = new Sample_DeferredShading;
    sp = OGRE_NEW SamplePlugin(s->getInfo()["Title"] + " Sample");
    sp->addSample(s);
    Root::getSingleton().installPlugin(sp);
}

extern "C" _OgreSampleExport void dllStopPlugin()
{
    Root::getSingleton().uninstallPlugin(sp);
    OGRE_DELETE sp;
    delete s;
}

上面是,一个Sample的小例子,dllStartPlugin()有个 Root::getSingleton().installPlugin,使用了Root单例把整个动态链接库里面的东西都交给了 主程序的 Root。

这里有个很神奇的问题,动态链接库里的单例Root 是如何做到 和主程序是同一个的?

奥秘就是,Root类前面加了 _OgreExport

#       if defined( OGRE_NONCLIENT_BUILD )
#           define _OgreExport __declspec( dllexport )
#       else
#           if defined( __MINGW32__ )
#               define _OgreExport
#           else
#               define _OgreExport __declspec( dllimport )
#           endif
#       endif

_OgreExport使用了宏实现不同编译器的动态符号导出,当Root单例以动态符号导出后,就可以实现 动态链接库 和 主程序 之间的共享这个单例Root了。然后,再由这个单例Root把这个插件里面的 代码和数据 绑定到主程序,这样主程序就可以随意调用动态链接库里面的代码了。

整体流程

  1. Root通过读取插件配置文件,把需要的加载模块的名字传给 loadPlugin 函数。
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
    if (std::find(mPluginLibs.begin(), mPluginLibs.end(), lib) == mPluginLibs.end())
    {
        mPluginLibs.push_back(lib);

        // Call startup function
                    #ifdef __GNUC__
                    __extension__
                    #endif
        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
}
  1. loadPlugin函数通过DynLibManager构建里面DynLib,并调用load函数加载动态链接库。
// OgreDynLibManager.cpp
DynLib* DynLibManager::load( const String& filename)
{
    DynLibList::iterator i = mLibList.find(filename);
    if (i != mLibList.end())
    {
        return i->second;
    }
    else
    {
        DynLib* pLib = OGRE_NEW DynLib(filename);
        pLib->load();        
        mLibList[filename] = pLib;
        return pLib;
    }
}
  1. 加载完动态链接库后,整个返回Root的loadPlugin 函数中,接着获取dllStartPlugin符号的函数,然后调用。

  2. 进入插件中的dllStartPlugin函数中,里面初始化一个继承Plugin类的扩展类,然后用单例Root的installPlugin函数把这个插件类添加进去。

  3. 完成整个插件的加载。

总结

Ogre这个插件的机制既有类似脚本语言的灵活性,也有原生C++的高效。在以后的一些开发的过程中,我可能会大量使用这种灵活的插件开发方法,不过还是值得考量的,毕竟使用脚本来扩展还是很大程度提高开发效率的。

至于缺陷,我倒是发现一个,就是 插件的在不同版本Ogre中(或者是同版本同操作系统的Ogre在不同编译器下)的兼容性就很难保证。比如,修改了一下里面的类的成员,类编译后的二进制结构就不一样了,这样的话,再调用动态链接库里面的东西,就有可能会出错了。

参考

http://blog.sina.com.cn/s/blog_9151e7300101fdfh.html
http://www.cnblogs.com/yzwalkman/archive/2012/12/20/2826687.html
http://www.cnblogs.com/bourneli/archive/2011/12/28/2305280.html

猜你喜欢

转载自blog.csdn.net/linsoft1994/article/details/51477022
1.9
今日推荐