插件其实就是基于动态库的软件扩展技术;
插件技术有三个核心:动态库技术,面向接口编程技术,运行时对象查找和生成.
动态库技术:
一个插件包就是一个动态库,每个动态库中可以导出很多个不同的接口的插件.每个导出的对象就是所谓的插件,没什么神奇的.
面向接口编程技术:
插件的接口接口必须保持自始至终保持一致,即对于插件的不同版本通过名字查找到的插件对象,其接口应该保持一致.基本上插件发布式只需要一个头文件和一个动态库就够了.
运行时对象查找和生成:
也就是运行时加载动态库,然后生成通过动态库的接口生成插件对象.然后在现有系统中使用这个对象.
--------------------------------------
例子:
插件接口:
class IOutput
{
public:
virtual void output(const char* pstr) = 0;
virtual void*getExt(const char* pextname, void* pargs) = 0;
};
导出插件包中的插件的函数:
extern "C" __declspec(dllexport)
void* createOutputObject(const char* pname, void* pargs);
这个函数要求使用者使用pname参数来指定用户希望创建的对象;
实现插件:
//输出到终端的插件:
class CTermOutput
{
...
public:
virtual void output(const char* pstr)
{
...
printf("%s", pstr);
}
virtual void* getExt(const char* pextname, void* pargs)
{
return (NULL);
}
};
//再实现一个输出到文件的插件:
class CFileOutput
{
...
FILE* m_pFile;
public:
virtual void output(const char* pstr)
{
...
fprintf(m_pFile, "%s", pstr);
}
virtual void* getExt(const char* pextname, void* pargs)
{
return (NULL);
}
};
//实现导出这两个不同的对象(当然这里的例子是接口是相同的)
extern "C" __declspec(dllexport)
void* createOutputObject(const char* pname, void* pargs)
{
...
if (0 == strcmp("TermOutput", pname))
{...
return new CTermOutput;
}
else
{
return new CFilePutput;
}
return (NULL);
};
//用户怎么使用呢?
IOutput* po = (IOutput*)createOutputObject("TermOutput", NULL);
IOutput* po = (IOutput*)createOutputObject("FileOutput", NULL);
这里的代码的特点:使用者不必要知道createOutputObject创建出来的对象是怎么实现的,只需要知道对象的名字就可以了,由于名字就是个字符串, 所以发布插件的时候只需要提供插件的名字,集成这个插件的软件就可以使用了.这就是运行时对象的查找和生成.
由于一般的软件处理字符串是相当容易的,所以这就导致基于插件的软件很容易修改,在线修改软件(Online update)也没问题.
--------------------------------------
导出不同的接口插件,方便升级,而且保持兼容性:
软件升级,而又要保持兼容性是比较难以处理的.然是如果采用这里介绍的插件技术就变得方便多了.
软件升级过程中往往涉及到,接口的修改,但是接口一旦修改而又要就新的发布包在旧系统中能够很好地运行就比较难处理了.
这里采用插件形式的可扩展接口,就可以保证升级后旧的业务可以较好运行,而新业务可以使用新功能,新接口.
以代码为例;
class IOutput
{
public:
virtual void output(const char* pstr) = 0;
virtual void*getExt(const char* pextname, void* pargs) = 0;
};
这里的getExt()成员函数具有与前面讲到的createOutputObject的类似的接口,所以他具有和createOutputObject一样的性质----通过名字查找生成对象.
加入由于业务的扩展,我们需要在原有接口上增加一个新函数:
virtual void* format(const char*, int, int) = 0;
由于就为了兼容就业务就不能直接在原有接口中增加新函数,怎么办呢?
提供一个新的接口:
class ITermOutputExt
{
public:
virtual void* format(const char*, int, int) = 0;
virtual void*getExt(const char* pextname, void* pargs) = 0;//新的接口最好也支持这个扩展函数,谁能保证这个新的接口不会再增加新函数呢?
};
然后CTermOutput的发布包中的getExt提供下面的实现:
void* CTermOutput::getExt(const char* pname, void* pargs)
{...
if (0 == strcmp(pname,"TermOutputExt")
{
return (new CTermOutputExt(pargs)); // pargs可能就是 IOutput, 具体实现成什么样这里就不好说了,场景不同实现方式不同;
}
}
这样新业务的开发者就可以这样用这这个新接口:
pOutputExt = pOutput->getExt("TermOutputExt", pOutput);
pOutputExt->format(...);
--------------------------------------
让createOutputObject()导出不同的接口:
由于createOutputObject()的作用是根据名字查找并生成对象,细心的同志可以看到这个函数的返回值是void*,
这意味着他可以导出其他的接口,而不限制在仅仅到处IOutput这个一个接口上;
--------------------------------------
使用插件设计系统时应该注意的问题:
1.性能问题:一个大的系统如果要求对启动速度很高,比冷双机系统,那么不推荐对系统大范围使用插件,
经过测试如果系统大面积使用插件,那么系统的速度将大打折扣,启动时间往往会延长好几倍甚至几十倍!
主要原因是载入插件时初始化动态库是比较慢的,少量还好,几百个动态库初始化起来就太慢太慢了.
2.这里介绍的都是机遇C++的插件的接口,但是这个插件当然不适合使用Java语言或者basic来调用了.
怎样让C++的插件接口在其他语言中也可正常超出了这里讨论的范围了.
插件技术有三个核心:动态库技术,面向接口编程技术,运行时对象查找和生成.
动态库技术:
一个插件包就是一个动态库,每个动态库中可以导出很多个不同的接口的插件.每个导出的对象就是所谓的插件,没什么神奇的.
面向接口编程技术:
插件的接口接口必须保持自始至终保持一致,即对于插件的不同版本通过名字查找到的插件对象,其接口应该保持一致.基本上插件发布式只需要一个头文件和一个动态库就够了.
运行时对象查找和生成:
也就是运行时加载动态库,然后生成通过动态库的接口生成插件对象.然后在现有系统中使用这个对象.
--------------------------------------
例子:
插件接口:
class IOutput
{
public:
virtual void output(const char* pstr) = 0;
virtual void*getExt(const char* pextname, void* pargs) = 0;
};
导出插件包中的插件的函数:
extern "C" __declspec(dllexport)
void* createOutputObject(const char* pname, void* pargs);
这个函数要求使用者使用pname参数来指定用户希望创建的对象;
实现插件:
//输出到终端的插件:
class CTermOutput
{
...
public:
virtual void output(const char* pstr)
{
...
printf("%s", pstr);
}
virtual void* getExt(const char* pextname, void* pargs)
{
return (NULL);
}
};
//再实现一个输出到文件的插件:
class CFileOutput
{
...
FILE* m_pFile;
public:
virtual void output(const char* pstr)
{
...
fprintf(m_pFile, "%s", pstr);
}
virtual void* getExt(const char* pextname, void* pargs)
{
return (NULL);
}
};
//实现导出这两个不同的对象(当然这里的例子是接口是相同的)
extern "C" __declspec(dllexport)
void* createOutputObject(const char* pname, void* pargs)
{
...
if (0 == strcmp("TermOutput", pname))
{...
return new CTermOutput;
}
else
{
return new CFilePutput;
}
return (NULL);
};
//用户怎么使用呢?
IOutput* po = (IOutput*)createOutputObject("TermOutput", NULL);
IOutput* po = (IOutput*)createOutputObject("FileOutput", NULL);
这里的代码的特点:使用者不必要知道createOutputObject创建出来的对象是怎么实现的,只需要知道对象的名字就可以了,由于名字就是个字符串, 所以发布插件的时候只需要提供插件的名字,集成这个插件的软件就可以使用了.这就是运行时对象的查找和生成.
由于一般的软件处理字符串是相当容易的,所以这就导致基于插件的软件很容易修改,在线修改软件(Online update)也没问题.
--------------------------------------
导出不同的接口插件,方便升级,而且保持兼容性:
软件升级,而又要保持兼容性是比较难以处理的.然是如果采用这里介绍的插件技术就变得方便多了.
软件升级过程中往往涉及到,接口的修改,但是接口一旦修改而又要就新的发布包在旧系统中能够很好地运行就比较难处理了.
这里采用插件形式的可扩展接口,就可以保证升级后旧的业务可以较好运行,而新业务可以使用新功能,新接口.
以代码为例;
class IOutput
{
public:
virtual void output(const char* pstr) = 0;
virtual void*getExt(const char* pextname, void* pargs) = 0;
};
这里的getExt()成员函数具有与前面讲到的createOutputObject的类似的接口,所以他具有和createOutputObject一样的性质----通过名字查找生成对象.
加入由于业务的扩展,我们需要在原有接口上增加一个新函数:
virtual void* format(const char*, int, int) = 0;
由于就为了兼容就业务就不能直接在原有接口中增加新函数,怎么办呢?
提供一个新的接口:
class ITermOutputExt
{
public:
virtual void* format(const char*, int, int) = 0;
virtual void*getExt(const char* pextname, void* pargs) = 0;//新的接口最好也支持这个扩展函数,谁能保证这个新的接口不会再增加新函数呢?
};
然后CTermOutput的发布包中的getExt提供下面的实现:
void* CTermOutput::getExt(const char* pname, void* pargs)
{...
if (0 == strcmp(pname,"TermOutputExt")
{
return (new CTermOutputExt(pargs)); // pargs可能就是 IOutput, 具体实现成什么样这里就不好说了,场景不同实现方式不同;
}
}
这样新业务的开发者就可以这样用这这个新接口:
pOutputExt = pOutput->getExt("TermOutputExt", pOutput);
pOutputExt->format(...);
--------------------------------------
让createOutputObject()导出不同的接口:
由于createOutputObject()的作用是根据名字查找并生成对象,细心的同志可以看到这个函数的返回值是void*,
这意味着他可以导出其他的接口,而不限制在仅仅到处IOutput这个一个接口上;
--------------------------------------
使用插件设计系统时应该注意的问题:
1.性能问题:一个大的系统如果要求对启动速度很高,比冷双机系统,那么不推荐对系统大范围使用插件,
经过测试如果系统大面积使用插件,那么系统的速度将大打折扣,启动时间往往会延长好几倍甚至几十倍!
主要原因是载入插件时初始化动态库是比较慢的,少量还好,几百个动态库初始化起来就太慢太慢了.
2.这里介绍的都是机遇C++的插件的接口,但是这个插件当然不适合使用Java语言或者basic来调用了.
怎样让C++的插件接口在其他语言中也可正常超出了这里讨论的范围了.