keePass---基于插件的密码管理器

     最近在研究KeePass的源代码,老外的大牛果然不同凡响,令人恐怖的宏到处都是。令我等菜鸟心服口服。不用我说,这个源代码确实有许多值得学习的地方,首先这是一个基于插件机制的程序。也就是只要按照它定义的规范就可以根据自己的需要为KeePass编写插件。这是不是很炫啊。记得我刚接触到vs studio开发环境时,装了源代码管理器插件。然后就可以直接在vs studio中使用源代码管理,这个功能让我感到非常的神奇,后来才知道vs studio是基于插件的。O(∩_∩)O而且KeePass界面也是很强大。

     KeePass插件机制是基于COM规范的,而且是绿色的。也就是所用的插件不用向系统注册,直接放到KeePass程序目录里就可以使用了。那这个插件机制是怎么实现的呢。如果不了解COM规范,可以参考msdn上关于COM规范的资料。

     首先,叙述下,KeePass是如何实现插件机制的?

     当KeePass启动时,首先在程序目录下查找所有的dll,并且对这些dll进行验证,如何验证呢。KeePass是通过dll文件的版本信息来识别的,也就是说如果dll的版本信息的Product是 KeePass Pulgin,则认为这个dll是KeePass的插件。否则的话就是不加载。根据KeePass的规范,凡是KeePass的dll插件,都要导出三个函数:KpCreateInstance(创建插件的函数)、KpInitializeLibrary(初始化插件的函数)、KpReleaseLibrary(释放插件函数)。

     BOOL CPluginManager::LoadAllPlugins() { std::vector<std_string> vCandidates = FindPluginCandidates(); IKpUnknown* pAPI = &CKpApiImpl::Instance(); for(size_t i = 0; i < vCandidates.size(); ++i) { KP_PLUGIN_INSTANCE kppi; CPluginManager::ClearStructure(&kppi); std_string strPluginPath = vCandidates[i]; if(_IsValidPlugin(strPluginPath.c_str(), &kppi) == false) continue; kppi.hinstDLL = LoadLibrary(strPluginPath.c_str()); if(kppi.hinstDLL == NULL) continue; // Call optional library initialization function LPKPLIBFUNC lpInit = (LPKPLIBFUNC)GetProcAddress(kppi.hinstDLL, KP_I_INITIALIZELIB); if(lpInit != NULL) { if(lpInit(pAPI) != S_OK) { FreePluginDllEx(kppi.hinstDLL); continue; } } LPKPCREATEINSTANCE lpCreate = (LPKPCREATEINSTANCE)GetProcAddress( kppi.hinstDLL, KP_I_CREATEINSTANCE); if(lpCreate == NULL) { FreePluginDllEx(kppi.hinstDLL); continue; } const HRESULT hr = lpCreate(IID_IKpPlugin, (void**)&kppi.pInterface, pAPI); if((hr != S_OK) || (kppi.pInterface == NULL)) { FreePluginDllEx(kppi.hinstDLL); continue; } kppi.dwPluginID = m_dwFreePluginID; ++m_dwFreePluginID; kppi.strPath = strPluginPath; m_plugins.push_back(kppi); } return this->_AssignPluginCommands(); }

 

    KeePass首先通过API函数GetProcAddr获取KpInitializeLibrary的函数对插件进行初始化,然后在获取KpCreateInstance函数创建插件。如果没有失败,则加入KeePass的插件列表中m_plugins.插件加载完了之后,KeePass调用BuildPluginMenu为插件建立用户接口。也就是在菜单Tools里建立插件的子菜单。首先插件要实现两个规范:GetMenuItems是获取插件里菜单资源,返回类型是KP_MENU_ITEM型指针,这个结构体是这样定义的:

      typedef struct
     {
           DWORD dwFlags; // 菜单标识

           DWORD dwState; // 菜单状态

           DWORD dwIcon;   //图标
           LPTSTR lpCommandString; ///菜单文本

            DWORD dwCommandID; ///菜单ID标识,注意这个是由KeePass使用的

             DWORD_PTR dwReserved; ///< Reserved for future use, must be 0.
      } KP_MENU_ITEM;

 

      KeePass根据KP_MENU_ITEM型指针建立插件的用户接口(菜单项),并且为每个菜单项分配一个CommandId。KeePass不知道插件到底有什么用户接口,那如何响应菜单项的命令消息呢。这就要根据CommandId来区分了。首先映射消息:

     ON_COMMAND_RANGE(WM_PLUGINS_FIRST, WM_PLUGINS_LAST, OnPluginMessage)

 

     也就是说把消息id在WM_PLUGINS_FIRST和WM_PLUGINS_LAST之间的消息都映射到函数OnPluginMessage,然后根据CommandId来区分每个消息的所对应的commandId找到对应的插件,并且通过接口OnMessage映射到插件的OnMessage进行处理。

  

     BOOL CPluginManager::CallPlugins(DWORD dwCode, LPARAM lParamW, LPARAM lParamL) { BOOL bRet = TRUE; if(dwCode == KPM_DIRECT_EXEC) // Call single plugin { for(size_t i = 0; i < m_plugins.size(); ++i) { KP_PLUGIN_INSTANCE* p = &m_plugins[i]; if((p->hinstDLL == NULL) || (p->pInterface == NULL)) continue; const DWORD dwNumCommands = p->pInterface->GetMenuItemCount(); KP_MENU_ITEM* pMenuItems = p->pInterface->GetMenuItems(); if((dwNumCommands == 0) || (pMenuItems == NULL)) continue; for(size_t j = 0; j < dwNumCommands; ++j) { if(pMenuItems[j].dwCommandID == (DWORD)lParamW) return p->pInterface->OnMessage(KPM_DIRECT_EXEC, lParamW, lParamL); } } } else // Call all plugins { for(size_t i = 0; i < m_plugins.size(); ++i) { KP_PLUGIN_INSTANCE* p = &m_plugins[i]; if((p->hinstDLL == NULL) || (p->pInterface == NULL)) continue; bRet &= p->pInterface->OnMessage(dwCode, lParamW, lParamL); } } return bRet; }

 

 

     好了,我们看到要实现插件机制,关键在于定义一组接口,通过这组接口建立起插件和主程序之间的交互。

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(keePass---基于插件的密码管理器)