win32消息映射8-mapslot链表节点的删除

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'库的最终源码。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值