Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9727814
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-04-23 21:35:36

用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的最后四行代码对所有的对象都一样。其余的部分则根据这个对象类型层次上的类不同而有所不同。
如果IUnknown的实现能形成规律,那么ATL便可以提供一种机制将这些具有共性的程序语句从代码中提取出来。实际上ATL做到了这一点,方法是通过提供灵活和可扩展的类层次,使得开发人员只要正确地说明所使用的类集,就可确定线程,服务器锁定和对象生命期行为。
如果看一看ATL实现的IUnknown类层次,你碰到的第一个参数化行为就是线程。ATL允许你构造被优化的对象和服务器,在相互转换STA和MTA的用法时,不用修改源代码。缺省情况下,ATL工程构造的是安全的MTA(MTA-safe)对象,除了要具备构造单STA对象所需的一切外,还需要附加代码和状态管理。通过定义适当的预处理指令,你能改变缺省的线程行为,从而支持单STA或多个基于STA的工程。
使用如下这个指令:
      /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的线程模型概念。ATL定义了三种线程模型类来实现线程安全行为所需的少数内联操作。每一种线程模型都输出两个静态函数,Increment 和Decrement。它们都有一个长整指针参数,并且对指定的线程模型实现最快的,合法的增减操作。每一种线程模型都输出两个嵌套类型定义(typedefs),即AutoCriticalSection 和CriticalSection,它们要么打包Win32的CRITICAL_SECTION,要么就是出于兼容性考虑的空类,没有实际实现。记住,所用临界区实际使用的实际类型依赖于特定的线程模型。
ATL实现的三种线程模型分别是CComMultiThreadModel,CComSingleThreadModel和 CComMultiThreadModelNoCS。CComMultiThreadModel使用InterlockedIncrement/InterlockedDecrement和实的CRITICAL_SECTIONS。CComSingleThreadModel使用更有效的 和-操作符及虚的CRITICAL_SECTIONS。
混合的CComMultiThreadModelNoCS除了使用虚的CRITICAL_SECTIONS外,还有InterlockedIncrement/InterlockedDecrement。第三种模型对于存在于MTAs中,但不需要任何数据成员的锁定的对象很有用。
ATL提供了两个类型定义,CComObjectThreadModel 和 CComGlobalsThreadModel,通过条件编译来保证对象和全局变量各自的效率及行为安全。依据所定义的三种预编译指令之一,每一类型名对应着以上描述的三种线程模型类之一。下表说明了这种对应关系,它依赖于ATL所使用的预处理指令。
ATL类型定义
阅读(275) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~