从0到1构建自己的插件系统–类注册
ps:纠正前面一个错误,之前的函数获取类的接口列表返回了数组,这是没有这个用法。可以使用std::vector代替,或者返回数组指针。
插件的核心
在之前的一篇文章中,我么了解到了建立插件的优势以及如何去建立一个插件类,但最终的目的还是为了能够在在插件dll中找到这个类并且创建它,这就是我们写插件的目的,这也是我们写插件的核心。类注册就是为了实现类的对象的创建工作,类注册的方式最核心的其实就是写回调函数(可以是全局函数或者是类的静态函数),这种方式可以理解为抽象工厂模式。
dll有两种加载模式,一种是静态加载模式(依赖lib文件)以及动态加载模式(直接通过windows api加载dll库)。对于插件使用的是动态库的加载模式。因此我们需要在加载的时候识别某个函数并执行它,完成初始化的工作。那么类注册的信息就应该写在这个函数中,在调用的时候就可以获取类的注册信息,那么类的信息是如何存储的呢?
类信息存储
类的信息在插件中主要有类名,对应的接口的ID列表,创建类对象的回调函数。当然有的时候为了识别一个接口的多个实现(我们可以给不同的实现加一个int的标记,也可以是通过真实类名来获取,但是真实的类名如果改了可能不容易识别,因此需要在接口文件中增加类名的宏定义)。
//类信息存储类,classentry类;
//类的创建回调函数申明,通过类的接口ID来查找实现类(不要要注意的是,如果一个接口的ID有多个实现类,那么会直接找第一个,如果要查找特定的类,在后面的程序中会有介绍;
typedef IUnkown *(*CreateClass)(long iid);
class ClassEntry //这个类需要导出;
{
private:
char* _class_name;//类名是有模块名字的;
std::vector<long> _clsid_list;//接口的ID列表,主要是为了多继承,单继承只有一个接口;
CreateClass _create_class;//创建类的函数指针;
int _flag;//通过一个接口的多个实现类标记,如果没有这个,就通过类名来判断;
public:
ClassEntry(const char* class_name,std::vector<long> clsid_list,CreateClass create_class,int flag=0)
{
_class_name=class_name;
_clsid_list=clsid_list;
_create_class=create_class;
_flag=flag;
}
const char* className() const
{
return _class_name;
}
std::vector<long> clsidList() const
{
return _clsid_list;
}
CreateClass createClass() const
{
return _create_class;
}
};
动态库初始化函数实现
我们要有个可以在外部识别的函数,这个函数的主要功能就是为了完成类的注册信息获取(可以一个类或者多个类)
基本函数实现
//返回的是std::vector列表,如果是想C语言调用,还是返回普通的数组指针(主要考虑野指针的问题)
extern "C" __declspec(dllexport) std::vector<ClassEntry> initClasses(const char* module_name)
{
static std::vector<ClassEntry*> module_class_list;
//NormalObject<Sample>是之前提到的类;
//sample类
auto cls=new ClassEntry(module_name+"sampe",Sample::clsidList(),(CreateClass)&dan::NormalObject<Sample>::create)
module_class_list.push_back(cls);
//后面可以用同样的方法存储相同的类;
...
return module_class_list;
}
上面的实现是个基本的逻辑,但是如果让写插件的人员去维护这个逻辑,还是挺麻烦的,因此我们可以使用宏的方式优化这个定义,即将这个函数分为三个宏(函数开头、函数中间实现部分、函数结尾)来实现
注册函数的优化
//定义注册函数的开头部分(包括一些不影响的全局变量、以及其他一些固定函数),输入模块名(这个模块名字一般跟插件的名字相同)
#define MODULE_BEGIN(module_name) \
const char* char_module_name = name; \
OUTCAPI const char* moduleName() { return char_module_name; }\ //返回模块的名称;
extern "C" __declspec(dllexport) void* initClasses() {\
#endif
//定义函数的中间部分(类的信息注册部分)
#define DEFINE_CLASSENTRY(cls) \
module_class_list.push_back(\
new ClassEntry(char_module_name + "."+ #cls, \
cls::clsidList(), (CreateClass)&NormalObject<cls>::create));
//定义函数的结束部分;
#define MODEL_END() return module_class_list;}
上面的三个宏就解决了我们initclasses每个插件开发者自己编写这个函数实现注册的问题。
编写插件
插件的内核逻辑我们已经解决了,现在从头梳理下二次开发者开发插件的步骤
- 新建一个普通的dll,编写接口文件(如果不需要新的接口,直接实现已有接口就可)
- 根据接口定义自己的实现类(注意在类的开头要加上一个宏CLASS_DEFINE)
- 新建一个register.cpp文件来编写以下代码
MODULE_BEGIN(SampleDll) \\函数开头
DEFINE_CLASSENTRY(Sample) \\类注册;
DEFINE_CLASSENTRY(...)//可以添加多个类
MODEL_END() \\模块结束标记
怎么样是不是so easy!!!