C++实现的一种插件体系结构

来源:互联网 发布:2017剑灵灵女捏脸数据 编辑:程序博客网 时间:2024/06/12 00:58

Ø  插件机制的好处:

1、增强代码的透明度与一致性:因为插件通常会封装第三方类库或是其他人编写的代码,需要清晰地定义出接口,用清晰一致的接口来面对所有事情。你的代码也不会被转换程序或是库的特殊定制需求弄得乱七糟。

2、改善工程的模块化:你的代码被清晰地分成多个独立的模块,可以把它们安置在子工程中的文件组中。这种解耦处理使得创建出的组件更加容易重用。

3、更短的编译时间:如果仅仅是为了解释某些类的声明,而这些类内部使用了外部库,编译器不再需要解析外部库的头文件了,因为具体实现是以私有的形式完成。

4、更换与增加组件:假如你需要向用户发布补丁,那么更新单独的插件而不是替代每一个安装了的文件更为有效。当使用新的渲染器或是新的单元类型来扩展你的游戏时,能过向引擎提供一组插件,可以很容易的实现。

5、在关闭源代码的工程中使用GPL代码:一般,假如你使用了GPL发布的代码,那么你也需要开放你的源代码。然而,如果把GPL组件封装在插件中,你就不必发布插件的源码。

 

Ø  插件系统工作原理:

在插件系统中,工程中的任何组件不再束缚于一种特定的实现(像渲染器既可以基于OpenGL,也可以选择Direct3D),它们会从框架代码中剥离出来,通过特定的方法被放入动态链接库之中。所谓的特定方法包括在框架代码中创建接口,这些接口使得框架与动态库解耦。插件提供接口的实现。我们把插件与普通的动态链接库区分开来是因为它们的加载方式不同:程序不会直接链接插件,而可能是在某些目录下查找,如果发现便进行加载。所有插件都可以使用一种共同的方法与应用进行联结。

 

Ø  插件设计实现 

插件系统在设计时,需要使插件在引擎中注册自己,或是用定制的实现替代引擎内部缺省实现。插件机制的实现包括两部分,即插件部分和框架的插件管理部分。

1、 插件的接口:

插件的接口是被引擎清楚定义的,而不是插件。引擎通过定义接口来指导插件做什么工作,插件具体实现功能。我们让插件注册自己的引擎接口的特殊实现。

以下代码为OGRE文件编码基类定义的接口:

class  Codec

{

    public:

//定义解析后数据基类

        class  CodecData

        {

        public:

            virtual String dataType() const {return "CodecData"; }

        };

    public:

          virtual~Codec();

        virtual DataStreamPtrcode(MemoryDataStreamPtr& input,

        virtual voidcodeToFile(MemoryDataStreamPtr& input, const String& outFileName,CodecDataPtr& pData) const = 0;

        typedef std::pair<MemoryDataStreamPtr,CodecDataPtr> DecodeResult;

        virtual DecodeResultdecode(DataStreamPtr& input) const = 0;

};

 

    class  ImageCodec : public Codec

    {

public:

//定义纹理解析后数据

        class  ImageData : public Codec::CodecData

        {

        public:

            size_t height;

            size_t width;

                     size_t depth;

            size_t size;

           

            ushort num_mipmaps;

            uint flags;

 

            PixelFormat format;

        public:

            String dataType() const

            {

                return "ImageData";

            }

        };

 

    public:

        String getDataType() const

        {

            return "ImageData";

        }

    };

(OGRE数据编码基类 OgreCodec.hOgreImageCodec.h)

 

以下代码为OGRE为EXR图片文件插件接口:

    class EXRCodec : public ImageCodec

    {

    public:

        EXRCodec();

        virtual ~EXRCodec();

        DataStreamPtrcode(MemoryDataStreamPtr& input, CodecDataPtr& pData) const;

       void codeToFile(MemoryDataStreamPtr& input, const String&outFileName, CodecDataPtr& pData) const;

        DecodeResult decode(DataStreamPtr&input) const;

        String magicNumberToFileExt(const char*magicNumberPtr, size_t maxbytes) const;

 

        String getType() const;

};

(OGRE插件类 OgreEXRCodec.h)

 

2、 插件的注册方法:

每个插件在编译完成后都是以dll的形式加载到引擎中,编译的 dll中必须存在一个全局的函数可以使引擎在加载时查找到该函数的指针,从而使插件得以在引擎中注册。

以下为OgreEXR插件暴漏给外部的注册接口:

Codec *mEXRCodec;

extern "C" voiddllStartPlugin(void)

{

    mEXRCodec = new EXRCodec;

    Codec::registerCodec( mEXRCodec );

}

extern "C" voiddllStopPlugin(void)

{

    Codec::unRegisterCodec( mEXRCodec );

    delete mEXRCodec;

}

(OgreEXRCodecDll.cpp)

在引擎中对该注册函数进行查找并进行注册:

DynLib*lib = DynLibManager::getSingleton().load( pluginName );

if(std::find(mPluginLibs.begin(), mPluginLibs.end(), lib) == mPluginLibs.end())

{

       mPluginLibs.push_back(lib);

DLL_START_PLUGINpFunc = (DLL_START_PLUGIN)lib->getSymbol("dllStartPlugin");

 

       if (!pFunc)

       OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,"Cannot find symbol dllStartPlugin in library " + pluginName,"Root::loadPlugin");

       pFunc();

}

(OgreRoot.cpp)

 

3、 插件管理--插件管理器:

接下来应该考虑插件如何在引擎中注册它们的工厂,引擎又如何实际地使用这些注册的插件。OGRE插件管理机制采取的是插件管理器的方式,提供一个单例模式的基类和一系列静态函数,这些静态函数即包括引擎对插件的查找也包括插件本身调用的注册和销毁。

class  Codec

{

    staticvoid registerCodec( Codec *pCodec );

static boolisCodecRegistered( const String& codecType );

    staticvoid unRegisterCodec( Codec *pCodec );

    static CodecIterator getCodecIterator(void);

    static StringVector getExtensions(void);

    static Codec* getCodec(const String&extension);

       static Codec* getCodec(char *magicNumberPtr, size_t maxbytes);

};

当引擎需要读取一个文件时,它会访问插件管理器,看哪些读取器已经通过插件注册了。然后要求插件管理器根据文件类型去选择插件,插件管理器甚至不需要知道实现细节。

插件管理器简单地在特定目录下加载所有dll文件,检查它们是否有一个名为dllStartPlugin()的导出函数。当然也可用xml文档来指定哪些插件要被加载。 

 

Ø  版本问题:

以上方法不强制要求把特定的实现放到插件中。假如引擎提供一个读档器的默认实现,以支持自定义文件包格式。可以把它放到引擎本身,当引擎启动时自动进行注册。

假如不小心的话,与引擎不匹配(例如,已经过时的)插件会被加载。子系统类的一些变化或是插件管理器的改变足以导致内存布局的改变,当不匹配的插件试图注册时可能发生冲突甚至崩溃。比较讨厌的是,这些在调试时难与发现。 幸运的是,辨认过时或不正确的插件非常容易。最可靠的是方法是在你的核心系统中放置一个预处理常量。任何插件都有一个函数,它可以返回这个常量给引擎:

#define MyEngineVersion 1;

 

extern intgetExpectedEngineVersion() {

  return MyEngineVersion;

}

在这个常量被编译到插件后,当引擎中的常量改变时,任何没有进行重新编译的插件它的 getExpectedEngineVersion()方法会返回以前的那个值。引擎可以根据这个值,拒绝加载不匹配的插件。为了使插件可以重新工作,必须重新编译它。当然,最大的危险是忘记了更新常量值。

0 0
原创粉丝点击