最初接触插件(Plugin)是当年开发客户端的时候使用的Ogre引擎,里面的设计另当时我这个小菜鸟惊叹不己,原来还可以这样组织代码。然后时隔多年,在Ogre的影响下,又进一步在NF引擎内加入了module和component,用以完善插件式遗漏的一些缺陷。
Ogre的插件式架构,是建立在动态库上的,windows为.dll,linux为.so,NF引擎也是如此(我在主导无双项目在开发的时候,又全部改成了静态库,改成静态库在NF引擎中只需要修改几十行代码即可)。使用插件来组织代码,好处非常多,比如同事分工协作方面,比如逻辑热更新方面(静态语言非脚本,比如使用c 热更新),比如维持代码的单纯度和统一管理规范方面,比如企业安全信息保密方面等等。
NF引擎的插件管理比Ogre略复杂,主要体现在每个插件内部都有module,然后所有的module在启动时又都注册到PluginManager接受PluginManager的管理。NFPluginLoader为程序的执行入口,他会自动查找启动目录下的Plugin.xml文件,然后加载里面配置过的plugin(或者自行传入名字让PluginLoader加载),例如:
Plugin.xml内部声明了每类服务器启动的时候需要加载的插件以及配置文件(NFDataCfg)路径,因为考虑到产品运维更偏向使用脚本批量启动服务器,因此AppID在脚本中可以传入,比如:
./NFPluginLoader_d -d Server=MasterServer ID=3
./NFPluginLoader_d -d PluginX.xml Server=MasterServer ID=3
插件加载程序的入口在文件NFPluginLoader.cpp的main函数,会先初始化NFCPluginManager,
然后调用NFCPluginManager进行初始化加载动态库(.dll .so),然后统一管理所有的插件,并统一进行初始化,反初始化,帧执行等操作,简略代码如下:
int main(int argc, char* argv[])
{
ProcessParameter(argc, argv);
NFCPluginManager::GetSingletonPtr()->Awake();
NFCPluginManager::GetSingletonPtr()->Init();
NFCPluginManager::GetSingletonPtr()->AfterInit();
NFCPluginManager::GetSingletonPtr()->CheckConfig();
NFCPluginManager::GetSingletonPtr()->ReadyExecute();
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
NFCPluginManager::GetSingletonPtr()->Execute();
}
NFCPluginManager::GetSingletonPtr()->BeforeShut();
NFCPluginManager::GetSingletonPtr()->Shut();
NFCPluginManager::GetSingletonPtr()->ReleaseInstance();
return 0;
}
因为任何一个插件(Plugin)必须继承自NFIPlugin类,它拥有NFIModule类所有的统一调用接口(任何模块,必须继承自NFIModule并拥有以下接口Awake, Init, AfterInit, Execute, BeforeShut, Shut, Finalize等统一接口):
class NFIModule
{
public:
virtual bool Awake()
{
return true;
}
virtual bool Init()
{
return true;
}
virtual bool AfterInit()
{
return true;
}
virtual bool CheckConfig()
{
return true;
}
virtual bool BeforeShut()
{
return true;
}
virtual bool Shut()
{
return true;
}
virtual bool ReadyExecute()
{
return true;
}
virtual bool Execute()
{
return true;
}
};
这些函数被调用的顺序,和NFCPluginManager启动的时候调用的顺序是一样的,每一个继承NFIModule的类(Mudole),都是按照此顺序运行。
所有的Module类的载体都是插件(动态库),因此为了夸平台,首先NF对要加载的动态库作了一个抽象,用NFCDynLib类来表示(这里向Ogre的作者致敬,几乎纯抄他的),一个动态库就是一个NFCDynLib类对象。同时NFCPluginManager类用来管理所有加载的NFCDynLib类对象,它负责根据动态库文件名对相应的库进行加载,并保存加载后的NFCDynLib对象指针,对于不同平台的插件区分加载,也是这里实现的,代码大概如下:
class NFCDynLib
{
public:
NFCDynLib(const std::string& strName)
{
mbMain = false;
mstrName = strName;
#ifdef NF_DEBUG_MODE
mstrName.append("_d");
#endif
#if NF_PLATFORM == NF_PLATFORM_WIN
mstrName.append(".dll");
#else
mstrName.append(".so");
#endif
printf("LoadPlugin:%s\n", mstrName.c_str());
}
~NFCDynLib()
{
}
bool Load()
{
std::string strLibPath = "./";
strLibPath = mstrName;
mInst = (DYNLIB_HANDLE)DYNLIB_LOAD(strLibPath.c_str());
return mInst != NULL;
}
bool UnLoad()
{
DYNLIB_UNLOAD(mInst);
return true;
}
const std::string& GetName(void) const
{
return mstrName;
}
const bool GetMain(void) const
{
return mbMain;
}
void* GetSymbol(const char* szProcName)
{
return (DYNLIB_HANDLE)DYNLIB_GETSYM(mInst, szProcName);
}
protected:
std::string mstrName;
bool mbMain;
DYNLIB_HANDLE mInst;
};
然后NFCPluginManager::Awake()函数在调用的时候,会先调用LoadPluginConfig()函数来动态库通过查找Plugin.xml内部配置的插件列表来加载需要的插件:
bool NFCPluginManager::LoadPluginConfig()
{
std::string strContent;
GetFileContent(mstrConfigName, strContent);
rapidxml::xml_document<> xDoc;
xDoc.parse<0>((char*)strContent.c_str());
rapidxml::xml_node<>* pRoot = xDoc.first_node();
rapidxml::xml_node<>* pAppNameNode = pRoot->first_node(mstrAppName.c_str());
if (!pAppNameNode)
{
NFASSERT(0, "There are no App ID", __FILE__, __FUNCTION__);
return false;
}
for (rapidxml::xml_node<>* pPluginNode = pAppNameNode->first_node("Plugin"); pPluginNode; pPluginNode = pPluginNode->next_sibling("Plugin"))
{
const char* strPluginName = pPluginNode->first_attribute("Name")->value();
mPluginNameMap.insert(PluginNameMap::value_type(strPluginName, true));
}
rapidxml::xml_node<>* pPluginConfigPathNode = pAppNameNode->first_node("ConfigPath");
if (!pPluginConfigPathNode)
{
NFASSERT(0, "There are no ConfigPath", __FILE__, __FUNCTION__);
return false;
}
if (NULL == pPluginConfigPathNode->first_attribute("Name"))
{
NFASSERT(0, "There are no ConfigPath.Name", __FILE__, __FUNCTION__);
return false;
}
mstrConfigPath = pPluginConfigPathNode->first_attribute("Name")->value();
return true;
}
其次会开始执行Init AfterInit等函数,每个函数都会调用内部NFIPlugin的同名接口,而在NFIPlugin内部又会调用所有的Module同名接口。比如Init函数做实例:
调用NFCPluginManager::Init内部会迭代所有的NFIPlugin,来调用NFIPlugin同名Init函数:
virtual bool NFCPluginManager::Init()
{
PluginInstanceMap::iterator itInstance = mPluginInstanceMap.begin();
for (itInstance; itInstance != mP
网友评论