從0到1構建自己的插件系統--類註冊

從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!!!
微信公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章