7. mapslot链表节点的删除
mapslot链表节点的删除可能是这一系列文章里,逻辑最为复杂的一部分。原因在于,一个mapslot链表的节点,不一定都存在于一个对象里面。且一个节点处理函数里面,若SendMessage有可能回到了这个链表处理,这里就存在一个嵌套调用的问题。
上一篇文章里说道,原先WndProc处理的逻辑,挪到了process_WM函数:
void wndproc::process_WM(wndbase &wnd, msg_struct &msg)
{
typedef std::pair<const msgmap_t *, const msgmap_t *> pair_type;
pair_type pr;
msgmap_t v;
v.message = msg.message;
mapslot_node *pSlot= wnd.m_mapslot_head.next;
for(;pSlot!= &wnd.m_mapslot_head;pSlot= pSlot->next)
{
const msgslot &slot= static_cast<const msgslot &>(*pSlot);
pr = std::equal_range(slot.entries, slot.entries + slot.entries_count, v);
if (pr.first != pr.second)
{
const msgmap_t &v= *pr.first;
if ( v.invoke(v, slot.wnd, msg ) )
return true;
}
}
return false;
}
这代码里存在隐患,在v.invoke里面,调用了映射的一个回调函数,若v.invoke返回true,这很好,什么事都没有。但若返回false,且把wnd干掉了,这时候,麻烦来了,在下一次for循环里访问pSlot->next或wnd.m_mapslot_head,立刻crash。
这问题的根源,在于m_mapslot_head和mapslot都是堆栈上的变量,没有办法控制它们的生存期,当它们所在堆栈被回收的时候,一定会消失。若想控制它们的生存期,只能在堆上构造。首先,为mapslot_node增加一个变量:ref_count,用于控制生存周期:
struct mapslot_node
{
mapslot_node *next, *prior;
volatile LONG ref_count;
mapslot_node() : ref_count(1)
{
next = prior = this;
}
// ...
};
一开始,把ref_count设成1。类似CComPtr,引入mapslot_ptr,管理mapslot对象:
template<typename T>
class mapslot_ptr
{
T *m_p;
friend wndproc;
mapslot_ptr & operator=(const mapslot_ptr<T> &);
public:
mapslot_ptr() : m_p(new T()){}
explicit mapslot_ptr(T *p):m_p(p){ ++m_p->ref_count; }
mapslot_ptr(cons mapslot_ptr<T> &rhs):m_p(rhs.m_p){ ++m_p->ref_count;}
~mapslot_str()
{
if( --m_p->ref_count ==0 )
delete m_p;
}
operator mapslot *() {return m_p;}
T * operator->() { return m_p; }
};
mapslot_ptr有上面几个函数足够了,因为mapslot只用作消息映射的节点,一般情况下,外面不应该直接操作mapslot。
对外接口由mapslot变成mapslot_ptr,2个和mapslot有关的宏也要做改变:
#define WABC_DECLARE_MSG_MAPEX(N) private: wabc::mapslot_ptr<wabc::mapslot> m_mapslot_wabc[N];
#define WABC_END_MSG_MAPEX(mapTo, mapFrom, slotIndex) \
}; \
enum{ slot_count= sizeof(m_mapslot_wabc)/sizeof(m_mapslot_wabc[0]) }; \
__wabc_static_assert(slotIndex < slot_count); \
(mapTo).map_msg<map_class>(m_mapslot_wabc[slotIndex], &(mapFrom), entries, countof(entries)); }
wndbase里的m_mapslot_head的类型,也变成指针类型:
class wndbase
{
mapslot_ptr<mapslot_node> m_mapslot_head;
// ...
};
剩下的事情就很好办了,wndproc::process_WM,对于正在使用的mapslot,先使ref_count加1,使用完毕后再减1,就能确保不会发生访问违例的问题。
void wndproc::process_WM(wndbase &wnd, msg_struct &msg)
{
typedef std::pair<const msgmap_t *, const msgmap_t *> pair_type;
pair_type pr;
msgmap_t v;
v.message = msg.message;
mapslot_ptr<mapslot_node> head(wnd.m_mapslot_head);
mapslot_node *pSlot= wnd.m_mapslot_head->next;
while(pSlot!= head.m_p)
{
const msgslot &slot= static_cast<const msgslot &>(*pSlot);
pr = std::equal_range(slot.entries, slot.entries + slot.entries_count, v);
if (pr.first != pr.second)
{
mapslot_ptr<mapslot> g(pSlot);
const msgmap_t &v= *pr.first;
if ( v.invoke(v, slot.wnd, msg ) )
return true;
pSlot= pSlot->next;
}
else
pSlot= pSlot->next;
}
return false;
}
注意pSlot= pSlot->next;这代码出现了两次,这是不能省略的。第一个pSlot= pSlot->next的时候,g的析构函数还没有触发,能确保pSlot使有效的,但g的析构函数一旦触发,就不能保证了。
同理还有wndproc::process_WM_CREATE的改变,这里就不再复制代码了。
解决了mapslot链表节点的删除,标记着这套消息映射的机制,走向了成熟,能适用于不同的环境。
总结:
从使用者的角度,并不需要理解里面实现的机制,但从实现者的角度,却必须理解每个实现的细节。从问题的产生,到问题的解决,这里面不断考验着人的分析能力。个人的能力在解决问题的过程中得到提升,各种知识点彼此融汇贯通,由此,各种概念不再是抽象的,而是具体的,不再是无根之木,无源之水。
请点击这里下载'wabc'库的最终源码。