從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!!!