Yate插件支持热插拔功能,即可以在系统运行时,动态卸载插件动态库,并在需要的时候,加载动态库;也可以修改动态库,并重新加载插件。从而实现插件的热插拔。
本文将以一个简单的插件为例,说明Yate插件热插拔功能的实现
插件的源码地址为:https://gitee.com/iseelgy/demo01
#include "Aux_cmd_define.h"
#include "Aux_yate_hal.h"
#include <functional>
//=====================================================================================================================
//=====================================================================================================================
//=====================================================================================================================
//=====================================================================================================================
using namespace TelEngine;
#define MY_PLUGIN_NAME "demo01"
namespace { // 使用匿名空间
//===============================
// 类定义
class MyPlugin : public Plugin
{
public:
MyPlugin() : Plugin(MY_PLUGIN_NAME)
, m_initialized(false)
{
}
~MyPlugin() {
}
virtual void initialize(void);
void onEngineStart();
void onEngineStop();
bool onYateMessage(Message& msg);
bool onDemoMessage(Message& msg);
bool unload();
private:
bool m_initialized;
YateMsgHandler* _debug_hdl=0;
// debug消息
bool onYateDebug(Message& msg);
protected:
std::vector<MessageHandler*> _msg_hanlers;
};
INIT_PLUGIN(MyPlugin);
//===============================
// 变量定义
static Configuration s_cfg;
//===============================
// 功能实现
void MyPlugin::initialize()
{
Output( "Initializing module " MY_PLUGIN_NAME);
s_cfg = Engine::configFile(MY_PLUGIN_NAME);
s_cfg.load();
int level = s_cfg.getIntValue("debug", "level", TelEngine::debugLevel());
withDebugEnabler()->debugLevel(level);
if (!m_initialized) {
// 设置初始化标记,避免多次调用情况出现问题
m_initialized = true;
// 订阅消息处理
YateMsgHandler* handler;
// 引擎启动消息
handler = new YateMsgHandler(MSG_ENGINE_START);
handler->_handler = [this](Message& msg) {
this->onEngineStart();
return false;
};
Engine::install(handler);
_msg_hanlers.push_back(handler);
// 引擎退出消息
handler = new YateMsgHandler(MSG_ENGINE_STOP);
handler->_handler = [this](Message& msg) {
this->onEngineStop();
return false;
};
Engine::install(handler);
_msg_hanlers.push_back(handler);
int priority = s_cfg.getIntValue("general", "priority", 100);
handler = new YateMsgHandler(MSG_YATE, priority);
handler->_handler = [this](Message& msg) {
return this->onYateMessage(msg);
};
Engine::install(handler);
_msg_hanlers.push_back(handler);
handler = new YateMsgHandler("demo01.cmd");
handler->_handler = [this](Message& msg) {
return this->onDemoMessage(msg);
};
Engine::install(handler);
_msg_hanlers.push_back(handler);
}
// 调试消息处理
int on = s_cfg.getBoolValue("debug", "on", false);
if (on) {
if (!_debug_hdl) {
int priority = s_cfg.getIntValue("debug", "priority", 100);
_debug_hdl = new YateMsgHandler("Yateshop.debug", priority);
_debug_hdl->_handler = [this](Message& msg) {
return this->onYateDebug(msg);
};
Engine::install(_debug_hdl);
}
}
else {
if (_debug_hdl) {
Engine::uninstall(_debug_hdl);
delete _debug_hdl;
_debug_hdl = nullptr;
}
}
}
void MyPlugin::onEngineStart()
{
//S_INFO("onEngineStart, ***************************");
//Y_WARN("onEngineStart, ***************************");
const String& shared = Engine::sharedPath();
const String& user = Engine::configPath(true);
const String& config = Engine::configPath(false);
}
void MyPlugin::onEngineStop()
{
S_INFO( "onEngineStop ") ;
}
bool MyPlugin::onYateMessage(Message& msg)
{
String Op = msg.getParam("Op");
if (Op == "demo.01"){
int what = msg.getIntValue("what");
int arg1 = msg.getIntValue("arg1");
int arg2 = msg.getIntValue("arg2");
Y_INFO( fmt::format("demo1 what {}, arg1 {}, arg2 {}", what, arg1, arg2) );
return true;
}
if (Op == "demo") {
int what = msg.getIntValue("what");
int arg1 = msg.getIntValue("arg1");
int arg2 = msg.getIntValue("arg2");
S_INFO("demo, ******");
return true;
}
if (Op == "demo.all") {
S_INFO("demo.all, ******");
return false;
}
return false;
}
bool MyPlugin::onDemoMessage(Message& msg)
{
S_INFO("onDemoMessage");
msg.setParam("hello", "yate");
return true;
}
bool MyPlugin::onYateDebug(Message& msg)
{
return false;
}
bool MyPlugin::unload()
{
// 注销消息处理器,以支持动态卸载模块
if (_debug_hdl) {
Engine::uninstall(_debug_hdl);
delete _debug_hdl;
_debug_hdl = nullptr;
}
auto i = _msg_hanlers.begin();
for (; i != _msg_hanlers.end(); i++) {
Engine::uninstall(*i);
delete *i;
}
_msg_hanlers.clear();
return true;
}
}
/*
* 支持模块内日志宏
*/
TelEngine::DebugEnabler * withDebugEnabler()
{
return &__plugin;
}
TE::Configuration * get_configuration()
{
return &s_cfg;
}
const char* get_app_name()
{
return s_cfg.getValue("general", "app.name", __plugin.name());
}
/*
*
* 支持模块动态卸载接口
*
*/
UNLOAD_PLUGIN(unloadNow)
{
if (unloadNow) {
// 动态卸载模块
if (!__plugin.unload()) {
S_INFO("unload module false, " << __plugin.name().safe());
return false;
}
S_INFO("unload module, " << __plugin.name().safe());
return true;
}
else {
// 检测是否能够动态卸载
return true;
}
}
热插拔插件需要导出函数 extern "C" bool _unload(bool unloadNow)
可以通过宏UNLOAD_PLUGIN(unloadNow)实现。
在卸载函数中,需要管理该插件使用的对象,并释放对象资源。如果订阅的消息处理,也需要取消订阅的消息,并释放分配的内存,返回true,以确认卸载完成。如果无法释放资源,可用通过返回false值,阻止卸载。
插件卸载是通过消息engine.command实现的,该功能需要定义line参数。
TE::String name = "demo01";
TE::String line = "module unload ";
line += name;
Message msg("engine.command");
msg.setParam("line", line);
if (Engine::dispatch(msg)) {
}
else {
}
插件加载载是通过消息engine.command实现的,该功能需要定义line参数
TE::String strSoPathname = "./modules/demo01.yate";
TE::String line = "module load ";
line += strSoPathname;
Message msg("engine.command");
msg.setParam("line", line);
if (Engine::dispatch(msg)) {
}
else {
}