分类:
2008-10-13 17:08:05
用ATL建立轻量级的COM对象
第三部分
作者:
:为什么要使用ATL。
:起步篇。
实现IUnknown
用纯粹的C++实现IUnknown相对来说比较简单。IUnknown实现之间的主要差别重点在于QueryInterface中将给出哪些接口。请看下列接口定义:
interface IMessageSource : IUnknown { HRESULT GetNextMessage([out] OLECHAR **ppwsz); } interface IPager : IUnknown { HRESULT SendMessage([in] const OLECHAR *pwsz); } interface IPager2 : IPager { HRESULT SendUrgentMessage(void); }这些C++类定义实现了三个接口:
class CPager : public IMessageSource, public IPager2 { LONG m_dwRef; public: CPager(void) : m_dwRef(0) {} virtual ~CPager(void) {} STDMETHODIMP QueryInterface(REFIID, void**); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); STDMETHODIMP GetNextMessage(OLECHAR **ppwsz); STDMETHODIMP SendMessage(const COLECHAR * pwsz); STDMETHODIMP SendUrgentMessage(void); };如果在堆中创建对象(也就是说用new操作符在内部创建)并且只用单线程公寓(STA)模式运行,下面是合理的AddRef 和Release实现:
STDMETHODIMP_(ULONG) CPager::AddRef() { return ++m_dwRef; } STDMETHODIMP_(ULONG) CPager::Release(){ ULONG result = -m_dwRef; if (result == 0) delete this; return result; }如果输出的对象是以多线程公寓(MTA)模式运行,则++和--操作符就必须用Win32的原子增量和减量(Increment/Decrement)例程调用来代替:
STDMETHODIMP_(ULONG) CPager::AddRef() { return InterlockedIncrement(&m_dwRef); } STDMETHODIMP_(ULONG) CPager::Release(){ ULONG result = InterlockedDecrement(&m_dwRef); if (result == 0) delete this; return result; }无论哪一种线程模式,下面的QueryInterface实现都是正确的:
STDMETHODIMP CPager::QueryInterface(REFIID riid, void **ppv) { if (riid == IID_IUnknown) *ppv = (IMessageSource*)this; else if (riid == IID_IMessageSource) *ppv = (IMessageSource*)this; else if (riid == IID_IPager) *ppv = (IPager*)this; else if (riid == IID_IPager2) *ppv = (IPager2*)this; else return (*ppv = 0), E_NOINTERFACE; ((IUnknown*)*ppv)->AddRef(); return S_OK; }QueryInterface的最后四行代码对所有的对象都一样。其余的部分则根据这个对象类型层次上的类不同而有所不同。
/D _ATL_SINGLE_THREADED来编译工程可以改变服务器缺省的线程模型,让它只支持一个基于STA的线程。它适合于进程外的且不创建自拥有线程的基于STA的服务器情况,当你用这个选项时,所有对ATL全局状态的存取将都是不加锁的,并发的。尽管此选项似乎很有效,但它实质上限制了ATL服务器只能是一个单线程的。
/D _ATL_APARTMENT_THREADED来编译工程可以改变服务器缺省的线程模型支持多个基于STA的线程。它适合于建立注册表项ThreadingModel=Apartment的进程内服务器。如果要创建基于STA的进程外服务器且还要建立附加的基于STA的线程,那么这个指令也是必须的。使用这个选项导致ATL用能安全存取线程的临界区来保护它的全局状态。
/D _ATL_FREE_THREADED可以创建与任何线程环境兼容的服务器。也就是说ATL的全局状态将在临界区中被锁定,并且每个对象将拥有它自己的私有临界区来保护它的实例状态。如果没有定义这些指令,则ATL头文件假设为使用_ATL_FREE_THREADED。
ATL类型定义 | _ATL_SINGLE_THREADED | _ATL_APARTMENT_THREADED | _ATL_FREE_THREADED |
CComGlobalsThreadModel | CComSingleThreadModel | CComMultiThreadModel | CComMultiThreadModel |
CComObjectThreadModel | CComSingleThreadModel | CComSingleThreadModel | CComMultiThreadModel |
只要给定了上述的线程模型类型层次,你就能将相应的参数化线程行为添加到任何COM类。请看下列代码:
参数化的线程 class CPager : public IPager { LONG m_dwRef; typedef CComObjectThreadModel _ThreadModel; _ThreadModel::CComAutoCriticalSection m_critsec; : : : : STDMETHODIMP_(ULONG) CPager::AddRef() { return _ThreadModel::Increment(&m_dwRef); } STDMETHODIMP_(ULONG) CPager::Release(){ ULONG res = _ThreadModel::Decrement(&m_dwRef); if (res == 0) delete this; return res; } STDMEHTHODIMP SendUrgentMessage() { // 保证只有一个线程 m_critsec.Lock(); // 实现任务 this->GenerateMessage(); this->WakeUpUser(); // 允许其它线程 m_critsec.Unlock(); return S_OK; } };使用缺省选项(_ATL_FREE_THREADED)的编译则将一个实临界区添加到对象,并执行Lock和Unlock方法将内联调用映射到EnterCriticalSection/LeaveCriticalSection API函数。同时,AddRef和Release方法将使用InterlockedIncrement/InterlockedDecrement来安全地改变这个对象的引用计数。
typedef CComObjectThreadModel _ThreadModel;细化到
typedef CComMultiThreadModelNoCS _ThreadModel;那么针对每一个对象,你不必付出CRITICAL_SECTION的开销(CComAutoCriticalSection 会映射到 CComFakeCriticalSection)就可以得到线程安全的AddRef和Release(将Increment 和 Decrement方法映射到InterlockedIncrement和InterlockedDecrement)。(待续)