概述
WebRTC 源码导读 000 · 目录
文中提到的代码引用自 libwebrtc M90 版本 https://github.com/aggresss/libwebrtc/tree/M90
sigslot 源文件位置:
└── src
└── rtc_base
└── third_party
└── sigslot
├── sigslot.cc
└── sigslot.h
libwebrtc 中经常会发现很多类继承 sigslot::has_slots<>
,可以看出 libwebrtc 中使用了信号槽语义来表达事件触发机制,先通过一个例子来了解 libwebrtc 中的 sigslot 使用方式:
#include "rtc_base/logging.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "system_wrappers/include/sleep.h"
class Sender {
public:
sigslot::signal2<std::string, int> emit_signal;
void Emit() {
static int nVal = 0;
char szVal[20] = {0};
std::snprintf(szVal, 20, "signal_%d", nVal);
emit_signal(szVal, nVal++);
}
};
class Receiver : public sigslot::has_slots<> {
public:
void OnSignal(std::string strMsg, int nVal) {
RTC_LOG(LS_INFO) << "Receive: " << strMsg.c_str() << " ==> " << nVal;
}
};
int main() {
rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE);
Sender sender;
Receiver recever;
sender.emit_signal.connect(&recever, &Receiver::OnSignal);
while (1) {
sender.Emit();
webrtc::SleepMs(1000);
}
return 0;
}
运行方法参考 https://github.com/aggresss/playground-libwebrtc/blob/playground/playground
通过上面示例可以发现,sigslot 的对外 API 相对比较简单,信号接收者只需要继承 has_slots
类即可拥有 slot 功能,信号发送者也只需要在实例化时确定信号参数,然后通过 connect 和 emit 方法来完成信号发送。但是 sigslots 内部实现时还是需要面临很多挑战,下面通过分析来理解 sigslots 是如何处理这些挑战的。
① 同步机制
在信号实例化时通过策略类模板的方式可以自定义同步方式,同时内置了三种同步机制:
- single_threaded
- multi_threaded_global
- multi_threaded_local
加锁的方式和 C++11
的内部实现机制很像,通过在代码块内(栈空间)实例化 lock_block
时加锁,析构时解锁,通过 C++ 的 RAII (Resource Acquisition Is Initialization) 机制实现锁操作。
template <class mt_policy>
class lock_block {
public:
mt_policy* m_mutex;
lock_block(mt_policy* mtx) : m_mutex(mtx) { m_mutex->lock(); }
~lock_block() { m_mutex->unlock(); }
};
② 连接的双向性
在信号发送前 signal 与 slot 之间需要建立一个连接,这个连接是双向的,也就是说 signal 和 slot 双方共同维护这个连接,连接和断开操作时双方都必须感知,同时任何一方的实例析构时另一方也应感知,同时删除内部维护的连接。所以 _signal_base_interface
和 has_slots_interface
都定义了对方发生变更时的操作接口:
class has_slots_interface {
private:
typedef void (*signal_connect_t)(has_slots_interface* self,
_signal_base_interface* sender);
typedef void (*signal_disconnect_t)(has_slots_interface* self,
_signal_base_interface* sender);
typedef void (*disconnect_all_t)(has_slots_interface* self);
const signal_connect_t m_signal_connect;
const signal_disconnect_t m_signal_disconnect;
const disconnect_all_t m_disconnect_all;
protected:
has_slots_interface(signal_connect_t conn,
signal_disconnect_t disc,
disconnect_all_t disc_all)
: m_signal_connect(conn),
m_signal_disconnect(disc),
m_disconnect_all(disc_all) {}
// Doesn't really need to be virtual, but is for backwards compatibility
// (it was virtual in a previous version of sigslot).
virtual ~has_slots_interface() {}
public:
void signal_connect(_signal_base_interface* sender) {
m_signal_connect(this, sender);
}
void signal_disconnect(_signal_base_interface* sender) {
m_signal_disconnect(this, sender);
}
void disconnect_all() { m_disconnect_all(this); }
};
class _signal_base_interface {
private:
typedef void (*slot_disconnect_t)(_signal_base_interface* self,
has_slots_interface* pslot);
typedef void (*slot_duplicate_t)(_signal_base_interface* self,
const has_slots_interface* poldslot,
has_slots_interface* pnewslot);
const slot_disconnect_t m_slot_disconnect;
const slot_duplicate_t m_slot_duplicate;
protected:
_signal_base_interface(slot_disconnect_t disc, slot_duplicate_t dupl)
: m_slot_disconnect(disc), m_slot_duplicate(dupl) {}
~_signal_base_interface() {}
public:
void slot_disconnect(has_slots_interface* pslot) {
m_slot_disconnect(this, pslot);
}
void slot_duplicate(const has_slots_interface* poldslot,
has_slots_interface* pnewslot) {
m_slot_duplicate(this, poldslot, pnewslot);
}
};
_signal_base_interface
和 has_slots_interface
都采用了 NVI (Non-Virtual Interface) 的手法,也就是没有定义虚函数,而是 protect
构造函数,继承时通过初始化列表赋值 private
成员,并提供 public
接口来调用接口函数的方式,这样做的目的主要是为了将与泛型无关的不变代码封装在这两个接口类中,通过分离编译期无关的代码来优化编译时间。
_signal_base
和 has_slots
在实现时都需要考虑 析构函数、复制构造函数、复制赋值函数的特殊化。
③ 成员函数指针的类型特殊性
C++ 的泛型中实现回调相对于 C 中实现略微复杂一些,主要是 C++ 的类成员函数指针不再是一个单纯的函数调用地址。
class _opaque_connection {
private:
typedef void (*emit_t)(const _opaque_connection*);
template <typename FromT, typename ToT>
union union_caster {
FromT from;
ToT to;
};
emit_t pemit;
has_slots_interface* pdest;
// Pointers to member functions may be up to 16 bytes for virtual classes,
// so make sure we have enough space to store it.
unsigned char pmethod[16];
public:
template <typename DestT, typename... Args>
_opaque_connection(DestT* pd, void (DestT::*pm)(Args...)) : pdest(pd) {
typedef void (DestT::*pm_t)(Args...);
static_assert(sizeof(pm_t) <= sizeof(pmethod),
"Size of slot function pointer too large.");
std::memcpy(pmethod, &pm, sizeof(pm_t));
typedef void (*em_t)(const _opaque_connection* self, Args...);
union_caster<em_t, emit_t> caster2;
caster2.from = &_opaque_connection::emitter<DestT, Args...>;
pemit = caster2.to;
}
has_slots_interface* getdest() const { return pdest; }
_opaque_connection duplicate(has_slots_interface* newtarget) const {
_opaque_connection res = *this;
res.pdest = newtarget;
return res;
}
// Just calls the stored "emitter" function pointer stored at construction
// time.
template <typename... Args>
void emit(Args... args) const {
typedef void (*em_t)(const _opaque_connection*, Args...);
union_caster<emit_t, em_t> caster;
caster.from = pemit;
(caster.to)(this, args...);
}
private:
template <typename DestT, typename... Args>
static void emitter(const _opaque_connection* self, Args... args) {
typedef void (DestT::*pm_t)(Args...);
pm_t pm;
std::memcpy(&pm, self->pmethod, sizeof(pm_t));
(static_cast<DestT*>(self->pdest)->*(pm))(args...);
}
};
_opaque_connection
不是一个模版类,两个公共方法是模板化的:构造函数
和 emit
,在保存回调方法时需要使用 union_caster
进行一次强制转换才能保存,虽然非模板类的成员变量不支持模板参数,但静态成员函数可以支持模板参数,即编译期实现静态成员函数重载(Overload) ,这就是 union_caster
的设计意图。不将 _opaque_connection
设计成模板类主要是为了避免模板导致的代码膨胀 (code bloat)。
typedef void (*emit_t)(const _opaque_connection*)
不是模板函数,所以可以保存为成员变量pemit
,通过union_caster
维护它和模板静态成员函数static void emitter(const _opaque_connection* self, Args... args)
之间的映射。pdest
和pmethod
为真正的回调类指针和成员函数指针。
参考文档
- webrtc的signal slot实现分析 · XXDK141 · CSDN
- webrtc 信号槽实现分析 · woder · cnblogs
最后
以上就是酷炫灰狼为你收集整理的WebRTC 源码导读 102 · sigslot的全部内容,希望文章能够帮你解决WebRTC 源码导读 102 · sigslot所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复