开源服务器框架NoahFrame分享 第二章:插件与模块

2017-10-31

最初接触插件(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

相关游戏

最新合集

相关文章

网友评论

    加载更多
    回复 [1楼 ]取消回复