青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

posts - 126,  comments - 73,  trackbacks - 0
本文假設(shè)你熟悉C++和COM。

摘要:
??? ATL——活動(dòng)模板庫(kù)(The Active Template Library),其設(shè)計(jì)旨在讓人們用C++方便靈活地開發(fā)COM對(duì)象。ATL本身相當(dāng)小巧靈活,這是它最大的優(yōu)點(diǎn)。用它可以創(chuàng)建輕量級(jí)的,自包含的,可復(fù)用的二進(jìn)制代碼,不用任何附加的運(yùn)行時(shí)DLLs支持。
??? 由于COM技術(shù)良好的口碑,越來(lái)越多的程序員已經(jīng)走進(jìn)或正在走進(jìn)COM的編程世界。它就像盛夏里的冰鎮(zhèn)啤酒,從來(lái)不會(huì)讓你失望。可惜作為一個(gè)C++程序員來(lái)說(shuō),C++從不與我分享COM的極致以及我對(duì)COM的情有獨(dú)鐘。
??? C++與COM之間若即若離,和平共處,一次又一次在每個(gè)對(duì)象中用同樣簡(jiǎn)潔的幾行代碼實(shí)現(xiàn)IUnknown。我敢肯定將來(lái)C++編譯器和鏈接器會(huì)實(shí)現(xiàn)C++對(duì)象和COM對(duì)象之間自然 的無(wú)意識(shí)的對(duì)應(yīng)和映射,目前這個(gè)環(huán)境只存在于實(shí)驗(yàn)室中,因此它肯定不是一個(gè)你我今天可以購(gòu)買的產(chǎn)品。眼下可得到的最接近這個(gè)環(huán)境的東西就是活動(dòng)模板庫(kù)——ATL。

為什么使用ATL?
???
ATL是在單層(single-tier)應(yīng)用逐漸過(guò)時(shí),分布式應(yīng)用逐漸成為主流這樣一個(gè)環(huán)境中誕生的, 它最初的版本是在四個(gè)C++頭文件中,其中有一個(gè)還是空的。它所形成的出色的構(gòu)架專門用于開發(fā)現(xiàn)代分布式應(yīng)用所需的輕量級(jí)COM組件。作為一個(gè)模塊化的標(biāo)準(zhǔn)組件,ATL不像MFC有厚重的基礎(chǔ)結(jié)構(gòu),省時(shí)好用的庫(kù)使得成百上千的程序員一次又一次輕松實(shí)現(xiàn)IUnknown 和IClassFactory。
??? ATL的構(gòu)架并不打算包羅萬(wàn)象,無(wú)所不能。其第一個(gè)版本對(duì)實(shí)現(xiàn)IUnknown,IClassFactory,IDispatch,IconnectionPointContainer及COM枚舉提供非常 到位的支持。第二個(gè)版本除了可以編寫ActiveX控件外,還對(duì)最初的第一個(gè)版本中ATL類進(jìn)行了增強(qiáng)。ATL不提供集合(collections)和串(strings)的處理 ,它假設(shè)你用標(biāo)準(zhǔn)的C++庫(kù)進(jìn)行這些處理;不支持ODBC——這個(gè)世界正在轉(zhuǎn)移到基于COM的不需要包裝的數(shù)據(jù)存取方式;不支持WinSock打包類--sockets本身也是新的東西;ATL也不支持完整的Win32 API打包類——ATL2.0的實(shí)現(xiàn)機(jī)制提供了對(duì)話框和WndProcs支持。此外ATL中沒(méi)有MFC中的文檔/視圖模型。取而代之的是ATL那更具伸縮性和靈活 性的通過(guò)COM接口(如ActiveX控件)與基于UI的對(duì)象之間的溝通模式。
??? 使用正確的工具非常關(guān)鍵。如果你正在編寫一個(gè)不可見的COM組件,那么ATL與MFC比起來(lái),從開發(fā)效率,可伸縮性,運(yùn)行時(shí)性能以及可執(zhí)行文件大小各方面來(lái)看,ATL可能 都是最好的選擇。對(duì)于現(xiàn)代基于ActiveX控件的用戶界面,ATL所產(chǎn)生的代碼也比MFC更小更快。另一方面,與MFC的類向?qū)啾龋珹TL需要更多的COM知識(shí)。ATL與STL一樣,對(duì)于單層應(yīng)用沒(méi)什么幫助,而MFC在這方面保持著它的優(yōu)勢(shì)。
??? ATL的設(shè)計(jì)在很大程度上來(lái)自STL的靈感,STL與所有ANSI/ISO兼容的C++編譯器一起已經(jīng)被納入成為標(biāo)準(zhǔn)C++庫(kù)的一部分。像STL一樣,ATL大膽使用C++模板。模板是C++中眾多具有爭(zhēng)議的特性之一。每每使用不當(dāng)都會(huì)導(dǎo)致執(zhí)行混亂,降低性能 和難以理解的代碼。明智地使用模板所產(chǎn)生的通用性效果和類型安全特性則是其它方法所望塵莫及的。ATL與STL一樣陷入了兩個(gè)極端。幸運(yùn)的是 在L大膽使用C++模板的同時(shí),編譯器和鏈接器技術(shù)也在以同樣的步伐向前發(fā)展。為當(dāng)前和將來(lái)的開發(fā)進(jìn)行STL和ATL的合理選擇。
??? 盡管模板在內(nèi)部得到廣泛的使用,但是在用ATL技術(shù)時(shí),你不用去敲入或關(guān)心那些模板中的尖括弧。因?yàn)锳TL本身帶有ATL對(duì)象向?qū)В▍⒁妶D一):






圖一 ATL 對(duì)象向?qū)?br />

??? 對(duì)象向?qū)Мa(chǎn)生大量基于ATL模板類缺省的對(duì)象實(shí)現(xiàn)代碼(即框架代碼)。這些缺省的對(duì)象類型如附表一所列。ATL對(duì)象向?qū)г试S任何人 快速建立COM對(duì)象并且在分分鐘之內(nèi)讓它運(yùn)行起來(lái),不用去考慮COM或ATL的細(xì)節(jié)問(wèn)題。當(dāng)然,為了能充分駕馭ATL,你必須掌握C++,模板和COM編程技術(shù)。對(duì)于大型的對(duì)象類,只要在ATL對(duì)象向?qū)a(chǎn)生的缺省實(shí)現(xiàn)(框架代碼)中加入方法實(shí)現(xiàn)來(lái)輸出定制接口,這也是大多數(shù)開發(fā)人員開始實(shí)現(xiàn)COM對(duì)象時(shí)的重點(diǎn)所在。
??? 初次接觸ATL時(shí),其體系結(jié)構(gòu)給人的感覺(jué)是神秘和不可思議。HelloATL是一個(gè)最簡(jiǎn)單的基于ATL的進(jìn)程內(nèi)服務(wù)器源代碼 以及用SDK(純粹用C++編寫)實(shí)現(xiàn)的同樣一個(gè)進(jìn)程內(nèi)服務(wù)器源代碼。在真正構(gòu)建出一個(gè)COM組件之前,代碼需要經(jīng)過(guò)反反復(fù)復(fù)多次斟酌和修改。對(duì)于想加速開發(fā)COM組件速度的主流組件開發(fā)人員來(lái)說(shuō),ATL體系結(jié)構(gòu)并不是什么大問(wèn)題,因?yàn)閷?duì)象向?qū)Мa(chǎn)生了所需要的全部框架代碼,只 要你加入方法定義即可。對(duì)于認(rèn)真的COM開發(fā)人員和系統(tǒng)編程人員來(lái)說(shuō),ATL提供了一個(gè)用C++建立COM組件的高級(jí)的,可擴(kuò)展的體系結(jié)構(gòu)。一旦你理解和掌握了這個(gè)體系結(jié)構(gòu)并能駕馭對(duì)象向?qū)В憔蜁?huì)看到ATL的表現(xiàn)能力和強(qiáng)大的功能 ,它完全可以和原始的COM編程技術(shù)媲美。
??? 另外一個(gè)使用ATL開發(fā)COM組件的理由是Visual C++ 5.0+集成開發(fā)環(huán)境(IDE)對(duì)ATL的高度支持。 微軟在Visual C++ 5.0+中將ATL所要用到的接口定義語(yǔ)言(IDL)集成到了C++編輯器中。步篇

??? 在本文的第一部分,我們簡(jiǎn)要介紹了ATL的一些背景知識(shí)以及ATL所面向的開發(fā)技術(shù)和環(huán)境。在這一部分 將開始走進(jìn)ATL,講述ATL編程的基本方法、原則和必須要注意的問(wèn)題。
??? 理解ATL最容易的方法是考察它對(duì)客戶端編程的支持。對(duì)于COM編程新手而言,一個(gè)棘手的主要問(wèn)題之一是正確管理接口指針的引用計(jì)數(shù)。COM的引用計(jì)數(shù)法則是沒(méi)有運(yùn)行時(shí)強(qiáng)制 性的,也就是說(shuō)每一個(gè)客戶端必須保證對(duì)對(duì)象的承諾。
??? 有經(jīng)驗(yàn)的COM編程者常常習(xí)慣于使用文檔中(如《Inside OLE》)提出的標(biāo)準(zhǔn)模式。調(diào)用某個(gè)函數(shù)或方法,返回接口指針,在某個(gè)時(shí)間范圍內(nèi)使用這個(gè)接口指針,然后釋放它。下面是使用這種模式的代碼例子:
void f(void) {
   IUnknown *pUnk = 0;
   // 調(diào)用 
   HRESULT hr = GetSomeObject(&pUnk);
   if (SUCCEEDED(hr)) {
   // 使用
     UseSomeObject(pUnk);
 // 釋放
     pUnk->Release();
   }
}
    
??? 這個(gè)模式在COM程序員心中是如此根深蒂固,以至于他們常常不寫實(shí)際使用指針的語(yǔ)句,而是先在代碼塊末尾敲入Release語(yǔ)句。這很像C程序員使用switch語(yǔ)句時(shí)的條件反射一樣,先敲入break再說(shuō)。
??? 其實(shí)調(diào)用Release實(shí)在不是什么可怕的負(fù)擔(dān),但是,客戶端程序員面臨兩個(gè)相當(dāng)嚴(yán)重的問(wèn)題。第一個(gè)問(wèn)題與獲得多接口指針有關(guān)。如果某個(gè)函數(shù)需要在做任何實(shí)際工作之前獲得三個(gè)接口指針,也就是說(shuō)在第一個(gè)使用指針的語(yǔ)句之前必須要由三個(gè)調(diào)用語(yǔ)句。在書寫代碼時(shí),這常常意味著程序員需要寫許多嵌套條件語(yǔ)句,如:
void f(void) {
  IUnknown *rgpUnk[3];
  HRESULT hr = GetObject(rgpUnk);
  if (SUCCEEDED(hr)) {
    hr = GetObject(rgpUnk + 1);
    if (SUCCEEDED(hr)) {
      hr = GetObject(rgpUnk + 2);
      if (SUCCEEDED(hr)) {
        UseObjects(rgpUnk[0], rgpUnk[1],
                     rgpUnk[2]);
        rgpUnk[2]->Release();
      }
      rgpUnk[1]->Release();
    }
    rgpUnk[0]->Release();
  }
}
    
??? 像這樣的語(yǔ)句常常促使程序員將TAB鍵設(shè)置成一個(gè)或兩個(gè)空格,甚至情愿使用大一點(diǎn)的顯示器。但事情并不總是按你想象的那樣,由于種種原因項(xiàng)目團(tuán)隊(duì)中的COM組件編程人員往往得不到 所想的硬件支持,而且在公司確定關(guān)于TAB鍵的使用標(biāo)準(zhǔn)之前,程序員常常求助于使用有很大爭(zhēng)議但仍然有效的“GOTO”語(yǔ)句:
void f(void) {
   IUnknown *rgpUnk[3];
   ZeroMemory(rgpUnk, sizeof(rgpUnk));
   if (FAILED(GetObject(rgpUnk))) 
     goto cleanup;
   if (FAILED(GetObject(rgpUnk+1))) 
     goto cleanup;
   if (FAILED(GetObject(rgpUnk+2))) 
     goto cleanup;
 
   UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
 
 cleanup:
   if (rgpUnk[0]) rgpUnk[0]->Release();
   if (rgpUnk[1]) rgpUnk[1]->Release();
   if (rgpUnk[2]) rgpUnk[2]->Release();
}    
這樣的代碼雖然不那么專業(yè),但至少減少了屏幕的水平滾動(dòng)。
使用以上這些代碼段潛在著更加棘手的問(wèn)題,那就是在碰到C++異常時(shí)。如果函數(shù)UseObjects丟出異常,則釋放指針的代碼被完全屏蔽掉了。 解決這個(gè)問(wèn)題的一個(gè)方法是使用Win32的結(jié)構(gòu)化異常處理(SEH)進(jìn)行終結(jié)操作:
void f(void) {
   IUnknown *rgpUnk[3];
   ZeroMemory(rgpUnk, sizeof(rgpUnk));
   __try {
    if (FAILED(GetObject(rgpUnk))) leave;
    if (FAILED(GetObject(rgpUnk+1))) leave;
    if (FAILED(GetObject(rgpUnk+2))) leave;
 
    UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
   } __finally {
    if (rgpUnk[0]) rgpUnk[0]->Release();
    if (rgpUnk[1]) rgpUnk[1]->Release();
    if (rgpUnk[2]) rgpUnk[2]->Release();
}
??? 可惜Win32 SHE在C++中的表現(xiàn)并不如想象得那么好。較好的方法是使用內(nèi)建的C++異常處理模型,同時(shí)停止使用沒(méi)有加工過(guò)的指針。標(biāo)準(zhǔn)C++庫(kù)有一個(gè)類:auto_ptr,在其析構(gòu)函數(shù)中定 死了一個(gè)操作指針的delete調(diào)用(即使在出現(xiàn)異常時(shí)也能保證調(diào)用)。與之類似,ATL有一個(gè)COM智能指針,CComPtr,它的析構(gòu)函數(shù)會(huì)正確調(diào)用Release。
??? CComPtr類實(shí)現(xiàn)客戶端基本的COM引用計(jì)數(shù)模型。CComPtr有一個(gè)數(shù)據(jù)成員,它是一個(gè)未經(jīng)過(guò)任何加工的COM接口指針。其類型被作為模板參數(shù)傳遞:
      CComPtr<IUnknown> unk;
      CComPtr<IClassFactory> cf;
??? 缺省的構(gòu)造函數(shù)將這個(gè)原始指針數(shù)據(jù)成員初始化為NULL。智能指針也有構(gòu)造函數(shù),它的參數(shù)要么是原始指針,要么是相同類型的智能參數(shù)。不論哪種情況,智能指針都調(diào)用AddRef控制引用。CComPtr的賦值操作符 既可以處理原始指針,也可以處理智能指針,并且在調(diào)用新分配指針的AddRef之前自動(dòng)釋放保存的指針。最重要的是,CComPtr的析構(gòu)函數(shù)釋放保存的接口(如果非空)。請(qǐng)看下列代碼:
void f(IUnknown *pUnk1, IUnknown *pUnk2) {
    // 如果非空,構(gòu)造函數(shù)調(diào)用pUnk1的AddRef 
    CComPtr unk1(pUnk1);
    // 如果非空,構(gòu)造函數(shù)調(diào)用unk1.p的AddRef
    CComPtr unk2 = unk1;
    // 如果非空,operator= 調(diào)用unk1.p的Release并且
    //如果非空,調(diào)用unk2.p的AddRef
    unk1 = unk2;
    //如果非空,析構(gòu)函數(shù)釋放unk1 和 unk2
}
??? 除了正確實(shí)現(xiàn)COM的AddRef 和 Release規(guī)則之外,CComPtr還允許實(shí)現(xiàn)原始和智能指針的透明操作,參見附表二所示。也就是說(shuō)下面的代碼按照你所想象的方式運(yùn)行:
void f(IUnknown *pUnkCO) {
    
    CComPtr cf;
    
    HRESULT hr;
    
    // 使用操作符 & 獲得對(duì) &cf.p 的存取
    hr = pUnkCO->QueryInterface(IID_IClassFactory,(void**)&cf);
    if (FAILED(hr)) throw hr;
    
    CComPtr unk;
    
    // 操作符 -> 獲得對(duì)cf.p的存取
    // 操作符 & 獲得對(duì) &unk.p的存取
    hr = cf->CreateInstance(0, IID_IUnknown, (void**)&unk);
    
    if (FAILED(hr)) throw hr;
    
    // 操作符 IUnknown * 返回 unk.p
    UseObject(unk);
    
    // 析構(gòu)函數(shù)釋放unk.p 和 cf.p
}
??? 除了缺乏對(duì)Release的顯式調(diào)用外,這段代碼像是純粹的COM代碼。有了CComPtr類的武裝,前面所遇到的麻煩問(wèn)題頓時(shí)變得簡(jiǎn)單了:
void f(void) {
CComPtr<IUnknown> rgpUnk[3];
if (FAILED(GetObject(&rgpUnk[0]))) return;
if (FAILED(GetObject(&rgpUnk[1]))) return;
if (FAILED(GetObject(&rgpUnk[2]))) return;
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
}
由于CComPtr對(duì)操作符重載用法的擴(kuò)展,使得代碼的編譯和運(yùn)行無(wú)懈可擊。
??? 假定模板類知道它所操縱的指針類型,你可能會(huì)問(wèn):那為什么智能指針不能在它的功能操作符或構(gòu)造函數(shù)中自動(dòng)調(diào)用QueryInterface,從而更有效地包裝IUnknown呢?在Visual C++ 5.0出來(lái)以前,沒(méi)有辦法將某個(gè)接口的GUID與它的本身的C++類型關(guān)聯(lián)起來(lái)——Visual C++ 5.0用私有的declspec將某個(gè)IID與一個(gè)接口定義綁定在一起。因?yàn)锳TL的設(shè)計(jì) 考慮到了它要與大量不同的C++編譯器一起工作,它需要用與編譯器無(wú)關(guān)的手段提供GUID。下面我們來(lái)探討另一個(gè)類——CComQIPtr類。
??? CComQIPtr與CComPtr關(guān)系很密切(實(shí)際上,它只增加了兩個(gè)成員函數(shù))。CComQIPtr必須要兩個(gè)模板參數(shù):一個(gè)是被操縱的指針類型 ,另一個(gè)是對(duì)應(yīng)于這個(gè)指針類型的GUID。例如,下列代碼聲明了操縱IDataObject 和IPersist接口的智能指針:
      CComQIPtr<IDataObject, &IID_IDataObject> do;
      CComQIPtr<IPersist, &IID_IPersist> p;
??? CComQIPtr的優(yōu)點(diǎn)是它有重載的構(gòu)造函數(shù)和賦值操作符。同類版本(例如,接受相同類型的接口)僅僅AddRef右邊的賦值/初始化操作。這實(shí)際上就是CComPtr的功能。異類版本(接受類型不一致的接口)正確調(diào)用QueryInterface來(lái)決定是否這個(gè)對(duì)象確實(shí)支持所請(qǐng)求的接口:
  
      void f(IPersist *pPersist) {
          CComQIPtr<IPersist, &IID_IPersist> p;
          // 同類賦值 - AddRef''s
          p = pPersist;
    
          CComQIPtr<IDataObject, &IID_IDataObject> do;
          // 異類賦值 - QueryInterface''s
          do = pPersist;
      }
 
??? 在第二種賦值語(yǔ)句中,因?yàn)閜Persist是非IDataObject *類型,但它是派生于IUnknown的接口指針,CComQIPtr通過(guò)pPersist調(diào)用QueryInterface來(lái)試圖獲得這個(gè)對(duì)象的IDataObject接口指針。如果QueryInterface調(diào)用成功,則此智能指針將含有作為結(jié)果的原始IDataObject指針。如果QueryInterface調(diào)用失敗,則do.p將被置為null。如果QueryInterface返回的HRESULT值很重要,但又沒(méi)有辦法從賦值操作獲得其值時(shí),則必須顯式調(diào)用QueryInterface。
??? 既然有了CComQIPtr,那為什么還要CComPtr呢?由幾個(gè)理由:首先,ATL最初的發(fā)布版本只支持CComPtr,所以它就一直合法地保留下來(lái)了。其二(也是最重要的理由),由于重載的構(gòu)造函數(shù)和賦值操作,對(duì)IUnknown使用CComQIPtr是非法的。因?yàn)樗蠧OM接口的類型定義都必須與IUnknown兼容。
      CComPtr<IUnknown> unk;
 
從功能上將它等同于
      CComQIPtr<IUnknown, &IID_IUnknown> unk;
 
前者正確。后者是錯(cuò)誤的用法。如果你這樣寫了,C++編譯器將提醒你改正。
??? 將CComPtr作為首選的另外一個(gè)理由可能是一些開發(fā)人員相信靜悄悄地調(diào)用QueryInterface,沒(méi)有警告,削弱了C++系統(tǒng)的類型。畢竟,C++在沒(méi)有進(jìn)行強(qiáng)制類型轉(zhuǎn)換的情況下不允許對(duì)類型不一致的原始指針 進(jìn)行賦值操作,所以為什么要用智能指針的道理也在這,幸運(yùn)的是開發(fā)人員可以選擇最能滿足需要的指針類型。
??? 許多開發(fā)人員將智能指針看成是對(duì)過(guò)于的復(fù)雜編程任務(wù)的簡(jiǎn)化。我最初也是這么認(rèn)為的。但只要留意它們使用COM智能指針的方法。就會(huì)逐漸認(rèn)識(shí)到它們引入的潛在危險(xiǎn)與它們解決的問(wèn)題一樣多。
關(guān)于這一點(diǎn),我用一個(gè)現(xiàn)成的使用原始指針的函數(shù)為例:
 
void f(void) {
   IFoo *pFoo = 0;
   HRESULT hr = GetSomeObject(&pFoo);
   if (SUCCEEDED(hr)) {
      UseSomeObject(pFoo);
      pFoo->Release();
   }
} 
將它自然而然轉(zhuǎn)換到使用CComPtr。
  void f(void) {
   CComPtr<IFoo> pFoo = 0;
   HRESULT hr = GetSomeObject(&pFoo);
   if (SUCCEEDED(hr)) {
      UseSomeObject(pFoo);
      pFoo->Release(); 
   }
}
 
??? 注意CComPtr 和 CComQIPtr輸出所有受控接口成員,包括AddRef和Release。可惜當(dāng)客戶端通過(guò)操作符->的結(jié)果調(diào)用Release時(shí),智能指針很健忘 ,會(huì)二次調(diào)用構(gòu)造函數(shù)中的Release。顯然這是錯(cuò)誤的,編譯器和鏈接器也欣然接受了這個(gè)代碼。如果你運(yùn)氣好的話,調(diào)試器會(huì)很快捕獲到這個(gè)錯(cuò)誤。
??? 使用ATL智能指針的另一個(gè)要引起注意的風(fēng)險(xiǎn)是類型強(qiáng)制轉(zhuǎn)換操作符對(duì)原始指針提供的訪問(wèn)。如果隱式強(qiáng)制轉(zhuǎn)換操作符的使用存在爭(zhēng)議。當(dāng) ANSI/ISO C++ 委員會(huì)在決定采用某個(gè)C++串類時(shí),他們明確禁止隱式類型轉(zhuǎn)換。而是要求必須顯式使用c_str函數(shù)在需要常量char *(const char *)的地方傳遞標(biāo)準(zhǔn)C++串。ATL提供了一種隱含式的類型轉(zhuǎn)換操作符順利地解決了這個(gè)問(wèn)題。通常,這個(gè)轉(zhuǎn)換操作符可以根據(jù)你的喜好來(lái)使用,允許你將智能指針傳遞到需要用原始指針的函數(shù)。
 void f(IUnknown *pUnk) { 
    CComPtr unk = pUnk;
    // 隱式調(diào)用操作符IUnknown *()
    CoLockObjectExternal(unk, TRUE, TRUE);
}
 
這段代碼能正確運(yùn)行,但是下面的代碼也不會(huì)產(chǎn)生警告信息,編譯正常通過(guò):
HRESULT CFoo::Clone(IUnknown **ppUnk) { 
   CComPtr unk;
   CoCreateInstance(CLSID_Foo, 0, CLSCTX_ALL,
   IID_IUnknown, (void **) &unk);
   // 隱式調(diào)用操作符IUnknown *()
   *ppUnk = unk;
   return S_OK;
}
??? 在這種情況下,智能指針(unk)對(duì)原始值針**ppUnk的賦值觸發(fā)了與前面代碼段相同的強(qiáng)制類型轉(zhuǎn)換。在第一個(gè)例子中,不需要用AddRef。在第二個(gè)例子中,必須要用AddRef。
??? 有關(guān)使用智能指針的更詳細(xì)一般信息,請(qǐng)參見Scott Meyer的《More Effective C++》(Addison-Wesley, 1995年出版)。國(guó)內(nèi)目前還沒(méi)有這本書的中譯本或影印本。有關(guān)COM智能指針的更多特定信息,請(qǐng)參見Don Box的一篇關(guān)于智能指針的專題文章

?第一部分:為什么要使用ATL。
??? 第二部分:起步篇。

實(shí)現(xiàn)IUnknown

??? 用純粹的C++實(shí)現(xiàn)IUnknown相對(duì)來(lái)說(shuō)比較簡(jiǎn)單。IUnknown實(shí)現(xiàn)之間的主要差別重點(diǎn)在于QueryInterface中將給出哪些接口。請(qǐng)看下列接口定義:
interface IMessageSource : IUnknown {
   HRESULT GetNextMessage([out] OLECHAR **ppwsz);
}
interface IPager : IUnknown {
   HRESULT SendMessage([in] const OLECHAR *pwsz);
}
interface IPager2 : IPager {
   HRESULT SendUrgentMessage(void);
}
這些C++類定義實(shí)現(xiàn)了三個(gè)接口:
 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);
};
?? 如果在堆中創(chuàng)建對(duì)象(也就是說(shuō)用new操作符在內(nèi)部創(chuàng)建)并且只用單線程公寓(STA)模式運(yùn)行,下面是合理的AddRef 和Release實(shí)現(xiàn):
STDMETHODIMP_(ULONG) CPager::AddRef() {
   return ++m_dwRef; 
}
STDMETHODIMP_(ULONG) CPager::Release(){
   ULONG result = -m_dwRef;
   if (result == 0)
     delete this;
   return result;
}
??? 如果輸出的對(duì)象是以多線程公寓(MTA)模式運(yùn)行,則++和--操作符就必須用Win32的原子增量和減量(Increment/Decrement)例程調(diào)用來(lái)代替:
STDMETHODIMP_(ULONG) CPager::AddRef() {
   return InterlockedIncrement(&m_dwRef); 
}
 STDMETHODIMP_(ULONG) CPager::Release(){
   ULONG result = InterlockedDecrement(&m_dwRef);
   if (result == 0)
     delete this;
   return result;
}
無(wú)論哪一種線程模式,下面的QueryInterface實(shí)現(xiàn)都是正確的:
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的最后四行代碼對(duì)所有的對(duì)象都一樣。其余的部分則根據(jù)這個(gè)對(duì)象類型層次上的類不同而有所不同。
??????? 如果IUnknown的實(shí)現(xiàn)能形成規(guī)律,那么ATL便可以提供一種機(jī)制將這些具有共性的程序語(yǔ)句從代碼中提取出來(lái)。實(shí)際上ATL做到了這一點(diǎn),方法是通過(guò)提供靈活和可擴(kuò)展的類層次,使得開發(fā)人員只要正確地說(shuō)明所使用的類集,就可確定線程,服務(wù)器鎖定和對(duì)象生命期行為。
????? 如果看一看ATL實(shí)現(xiàn)的IUnknown類層次,你碰到的第一個(gè)參數(shù)化行為就是線程。ATL允許你構(gòu)造被優(yōu)化的對(duì)象和服務(wù)器,在相互轉(zhuǎn)換STA和MTA的用法時(shí),不用修改源代碼。缺省情況下,ATL工程構(gòu)造的是安全的MTA(MTA-safe)對(duì)象,除了要具備構(gòu)造單STA對(duì)象所需的一切外,還需要附加代碼和狀態(tài)管理。通過(guò)定義適當(dāng)?shù)念A(yù)處理指令,你能改變?nèi)笔〉木€程行為,從而支持單STA或多個(gè)基于STA的工程。
使用如下這個(gè)指令:
      /D _ATL_SINGLE_THREADED
來(lái)編譯工程可以改變服務(wù)器缺省的線程模型,讓它只支持一個(gè)基于STA的線程。它適合于進(jìn)程外的且不創(chuàng)建自擁有線程的基于STA的服務(wù)器情況,當(dāng)你用這個(gè)選項(xiàng)時(shí),所有對(duì)ATL全局狀態(tài)的存取將都是不加鎖的,并發(fā)的。盡管此選項(xiàng)似乎很有效,但它實(shí)質(zhì)上限制了ATL服務(wù)器只能是一個(gè)單線程的。
使用如下這個(gè)指令:
      /D _ATL_APARTMENT_THREADED
來(lái)編譯工程可以改變服務(wù)器缺省的線程模型支持多個(gè)基于STA的線程。它適合于建立注冊(cè)表項(xiàng)ThreadingModel=Apartment的進(jìn)程內(nèi)服務(wù)器。如果要?jiǎng)?chuàng)建基于STA的進(jìn)程外服務(wù)器且還要建立附加的基于STA的線程,那么這個(gè)指令也是必須的。使用這個(gè)選項(xiàng)導(dǎo)致ATL用能安全存取線程的臨界區(qū)來(lái)保護(hù)它的全局狀態(tài)。
使用如下這個(gè)指令:
      /D _ATL_FREE_THREADED
可以創(chuàng)建與任何線程環(huán)境兼容的服務(wù)器。也就是說(shuō)ATL的全局狀態(tài)將在臨界區(qū)中被鎖定,并且每個(gè)對(duì)象將擁有它自己的私有臨界區(qū)來(lái)保護(hù)它的實(shí)例狀態(tài)。如果沒(méi)有定義這些指令,則ATL頭文件假設(shè)為使用_ATL_FREE_THREADED。
??? 為了在ATL中正確使用線程,必須理解ATL的線程模型概念。ATL定義了三種線程模型類來(lái)實(shí)現(xiàn)線程安全行為所需的少數(shù)內(nèi)聯(lián)操作。每一種線程模型都輸出兩個(gè)靜態(tài)函數(shù),Increment 和Decrement。它們都有一個(gè)長(zhǎng)整指針參數(shù),并且對(duì)指定的線程模型實(shí)現(xiàn)最快的,合法的增減操作。每一種線程模型都輸出兩個(gè)嵌套類型定義(typedefs),即AutoCriticalSection 和CriticalSection,它們要么打包Win32的CRITICAL_SECTION,要么就是出于兼容性考慮的空類,沒(méi)有實(shí)際實(shí)現(xiàn)。記住,所用臨界區(qū)實(shí)際使用的實(shí)際類型依賴于特定的線程模型。
??? ATL實(shí)現(xiàn)的三種線程模型分別是CComMultiThreadModel,CComSingleThreadModel和 CComMultiThreadModelNoCS。CComMultiThreadModel使用InterlockedIncrement/InterlockedDecrement和實(shí)的CRITICAL_SECTIONS。CComSingleThreadModel使用更有效的++和-操作符及虛的CRITICAL_SECTIONS。
混合的CComMultiThreadModelNoCS除了使用虛的CRITICAL_SECTIONS外,還有InterlockedIncrement/InterlockedDecrement。第三種模型對(duì)于存在于MTAs中,但不需要任何數(shù)據(jù)成員的鎖定的對(duì)象很有用。
??? ATL提供了兩個(gè)類型定義,CComObjectThreadModel 和 CComGlobalsThreadModel,通過(guò)條件編譯來(lái)保證對(duì)象和全局變量各自的效率及行為安全。依據(jù)所定義的三種預(yù)編譯指令之一,每一類型名對(duì)應(yīng)著以上描述的三種線程模型類之一。下表說(shuō)明了這種對(duì)應(yīng)關(guān)系,它依賴于ATL所使用的預(yù)處理指令。
ATL類型定義_ATL_SINGLE_THREADED_ATL_APARTMENT_THREADED_ATL_FREE_THREADED
CComGlobalsThreadModelCComSingleThreadModelCComMultiThreadModelCComMultiThreadModel
CComObjectThreadModelCComSingleThreadModelCComSingleThreadModelCComMultiThreadModel

只要給定了上述的線程模型類型層次,你就能將相應(yīng)的參數(shù)化線程行為添加到任何COM類。請(qǐng)看下列代碼:

  參數(shù)化的線程

 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() {
 // 保證只有一個(gè)線程  
     m_critsec.Lock(); 
 // 實(shí)現(xiàn)任務(wù)
     this->GenerateMessage();
     this->WakeUpUser();
 // 允許其它線程
     m_critsec.Unlock();
     return S_OK;
   }
 }; 
使用缺省選項(xiàng)(_ATL_FREE_THREADED)的編譯則將一個(gè)實(shí)臨界區(qū)添加到對(duì)象,并執(zhí)行Lock和Unlock方法將內(nèi)聯(lián)調(diào)用映射到EnterCriticalSection/LeaveCriticalSection API函數(shù)。同時(shí),AddRef和Release方法將使用InterlockedIncrement/InterlockedDecrement來(lái)安全地改變這個(gè)對(duì)象的引用計(jì)數(shù)。
????? 如果前面的代碼是用_ATL_APARTMENT_THREADED 或者 _ATL_SINGLE_ THREADED選項(xiàng)編譯的,則m_critsee數(shù)據(jù)成員將為空,Lock和Unlock內(nèi)聯(lián)例程將變成虛操作,并且AddRef和Release方法將使用++和--操作符。如果這個(gè)對(duì)象是一個(gè)ActiveX控件且其線程模型為Apartment (ThreadingModel=Apartment)的進(jìn)程內(nèi)服務(wù)器,則這將是小而快的代碼。
????? 有時(shí)構(gòu)造以MTA模式(即它的AddRef和Release必須是線程安全的)運(yùn)行,但不需要附加鎖定的對(duì)象很有用。對(duì)于這種類型的對(duì)象,混合型的CComMultiThreadModelNoCS很適合。
通過(guò)將類的類型定義從:
      typedef CComObjectThreadModel _ThreadModel;
細(xì)化到
      typedef CComMultiThreadModelNoCS _ThreadModel;
那么針對(duì)每一個(gè)對(duì)象,你不必付出CRITICAL_SECTION的開銷(CComAutoCriticalSection 會(huì)映射到 CComFakeCriticalSection)就可以得到線程安全的AddRef和Release(將Increment 和 Decrement方法映射到InterlockedIncrement和InterlockedDecrement)。

實(shí)現(xiàn)接口

??? 現(xiàn)在你已經(jīng)積累了一些關(guān)于ATL線程模型方面的知識(shí),下面我們來(lái)討論ATL如何實(shí)現(xiàn)IUnknown。ATL最不直觀的(同時(shí)也是最強(qiáng)大的)一個(gè)方面就是你要實(shí)現(xiàn)的類事實(shí)上都是不能被直接實(shí)例化的抽象類。實(shí)現(xiàn)一個(gè)從通用的IUnknown派生的C++類。但是在確定對(duì)象的運(yùn)行環(huán)境之前,QueryInterface,AddRef 和 Release是不會(huì)有實(shí)質(zhì)性代碼的。這種靈活性使開發(fā)人員能實(shí)現(xiàn)對(duì)象的關(guān)鍵功能,如COM的聚合支持,tear-offs,堆和棧分配,服務(wù)器鎖定等等。下圖展示了一個(gè)典型的基于ATL的類層次。

?????????????????????????? 圖四 典型的基于ATL的類層次

從下面的代碼可以看出,ATL中實(shí)現(xiàn)IUnknown的關(guān)鍵在于CComObjectRootBase 和 CComObjectRootEx。
?? CComObjectRoot?


?class CComObjectRootBase {
?public:
?// C++ 構(gòu)造函數(shù)
?? CComObjectRootBase() { m_dwRef = 0L; }
?
?// ATL 偽構(gòu)造函數(shù)和偽析構(gòu)函數(shù)
?? HRESULT FinalConstruct() { return S_OK; }?
?? void FinalRelease() {}
?
?// 內(nèi)部Unknown函數(shù)(由派生類提供的InternalAddRef/Release)
?? static HRESULT WINAPI InternalQueryInterface(void* pThis,?
???????????? const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) {
???? HRESULT hRes = AtlInternalQueryInterface(pThis,pEntries,iid,ppvObject);
???? return _ATLDUMPIID(iid, pszClassName, hRes);
?? }
?
?// 外部Unknown函數(shù)
?? ULONG OuterAddRef() { return m_pOuterUnknown->AddRef(); }
?? ULONG OuterRelease() { return m_pOuterUnknown->Release(); }
?? HRESULT OuterQueryInterface(REFIID iid, void ** ppvObject)?
?? { return m_pOuterUnknown->QueryInterface(iid, ppvObject); }
?
?// ATL 創(chuàng)建者的鉤子例程
?? void SetVoid(void*) {}
?? void InternalFinalConstructAddRef() {}
?? void InternalFinalConstructRelease() {}
?
?// ATL 接口映射輔助函數(shù)
?? static HRESULT WINAPI _Break(?????? void*, REFIID, void**, DWORD);
?? static HRESULT WINAPI _NoInterface( void*, REFIID, void**, DWORD);
?? static HRESULT WINAPI _Creator(???? void*, REFIID, void**, DWORD);
?? static HRESULT WINAPI _Delegate(??? void*, REFIID, void**, DWORD);
?? static HRESULT WINAPI _Chain(?????? void*, REFIID, void**, DWORD);
?? static HRESULT WINAPI _Cache(?????? void*, REFIID, void**, DWORD);
?
?// 實(shí)際的引用計(jì)數(shù)或者指針?lè)祷氐秸鎸?shí)的Unknown
?? union {
???? long m_dwRef;
???? IUnknown* m_pOuterUnknown;
?? };
?};
?
?template <class ThreadModel>
?class CComObjectRootEx : public CComObjectRootBase {
?public:
?? typedef ThreadModel _ThreadModel;
?? typedef _ThreadModel::AutoCriticalSection _CritSec;
?
?// 內(nèi)部 Unknown 函數(shù)(InternalQueryInterface 由 CComObjectRootBase提供)
?? ULONG InternalAddRef()? { return _ThreadModel::Increment(&m_dwRef); }
?? ULONG InternalRelease()??????? { return _ThreadModel::Decrement(&m_dwRef); }
?
?// 對(duì)象級(jí)的鎖定操作
?? void Lock() {m_critsec.Lock();}
?? void Unlock() {m_critsec.Unlock();}
?private:
?? _CritSec m_critsec;
?};

這兩個(gè)類提供了三個(gè)方法:OuterQueryInterface,OuterAddRef 和 OuterRelease,它們被用來(lái)將IUnknown的功能委派給外部實(shí)現(xiàn)。當(dāng)實(shí)現(xiàn)COM的聚合和tear-offs時(shí)要用到這些方法。其它三個(gè)方法--InternalQueryInterface,InternalAddRef和 InternalRelease的作用是實(shí)現(xiàn)本身的引用計(jì)數(shù)以及對(duì)象接口的查詢或?qū)Ш健?br />??? CComObjectRootEx是個(gè)模板類,允許你針對(duì)這個(gè)類指定使用哪種ATL線程模型。(如果你想要進(jìn)行條件編譯,則使用CComObjectRoot就可以了,它是一個(gè)針對(duì)CComObjectRootEx<CComObjectThreadModel>的類型定義。)CComObjectRootEx從CComObjectRootBase中派生其大多數(shù)功能,它是個(gè)相當(dāng)袖珍的類,只包含一個(gè)聯(lián)合類型的數(shù)據(jù)成員:
 union {
   long m_dwRef; 
   IUnknown *m_pOuterUnknown;
 }; 
? 根據(jù)使用這個(gè)類的實(shí)際方式,聯(lián)合中的成員將被用于保存給定類實(shí)例的生命周期。大多數(shù)情況下要用到m_dwRef,m_pOuterUnknown只有在支持聚合或tear-offs時(shí)用到。CComObjectRootBase提供了OuterQueryInterface,OuterAddRef和OuterRelease方法,通過(guò)m_pOuterUnknown成員轉(zhuǎn)發(fā)IUnknown請(qǐng)求。
? 反過(guò)來(lái),CComObjectRootEx提供InternalAddRef 和InternalRelease方法根據(jù)模板參數(shù)傳遞的線程模型來(lái)實(shí)際增減m_dwRef變量得值。注意這些例程只是增減這個(gè)變量,而沒(méi)有真正刪除這個(gè)對(duì)象。這是因?yàn)榇藢?duì)象的分配策略將由派生類中提供,派生類將使用這些例程來(lái)調(diào)整引用計(jì)數(shù)。
? CComObjectRoot層次最引人注目的是它的QueryInterface實(shí)現(xiàn)函數(shù),它被作為CComObjectRootBase的方法(InternalQueryInterface)輸出:
static HRESULT WINAPI 
 CComObjectRootBase::InternalQueryInterface(void *pThis, 
                      const _ATL_INTMAP_ENTRY *pEntries, 
                      REFIID riid, void **ppv);
      
     
使用ATL實(shí)現(xiàn)IUnknown的每一個(gè)類必須制定一個(gè)接口映射來(lái)提供InternalQueryInterface。ATL的接口映射是IID/DWORD/函數(shù)指針數(shù)組,它指示當(dāng)QueryInterface請(qǐng)求一個(gè)給定的IID時(shí)要采取什么樣的行動(dòng)。其類型都是_ATL_INTMAP_ENTRY。
struct _ATL_INTMAP_ENTRY {
   const IID* piid;  // 接口ID (IID)
   DWORD dw;         // 多用途值
   HRESULT (*pFunc)(void*, REFIID, void**, DWORD); 
};      

這個(gè)結(jié)構(gòu)的第三個(gè)成員pFunc的取值有三種情況。如果pFunc等于常量_ATL_SIMPLEMAPENTRY,則結(jié)構(gòu)成員dw為對(duì)象偏移量,這時(shí)不需要函數(shù)調(diào)用,并且InternalQueryInterface完成下列操作:

 (*ppv = LPBYTE(pThis) + pEntries[n].dw)->AddRef();
這個(gè)偏移量的初始化通常參照基類接口的偏移量。如果pFunc非空且不等于_ATL_SIMPLEMAPENTRY,則它指向的函數(shù)將被調(diào)用,將這個(gè)指針作為第一個(gè)參數(shù)傳遞給對(duì)象而第二個(gè)參數(shù)是多用途值dw。
return pEntries[n].pFunc(pThis, riid, ppv, 
                          pEntries[n].dw);
這個(gè)接口映射的最后一個(gè)入口將使用pFunc值,指示映射的結(jié)束。 如果沒(méi)有在映射中發(fā)現(xiàn)任何接口則InternalQueryInterface 會(huì)返回E_NOINTERFACE。 接口映射通常為ATL的接口映射宏。ATL提供17個(gè)不同的宏,它們支持大多數(shù)用于實(shí)現(xiàn)接口的普通技術(shù)(多繼承,嵌套類,聚合或者tear-offs。)這些宏及其相應(yīng)的原始代碼請(qǐng)參見附表三。下面是一個(gè)使用CComObjectRootEx和接口映射實(shí)現(xiàn)IPager2 和IMessageSource類的例子:
class CPager 
  : public IMessageSource, public IPager2,
    public CComObjectRootEx<CComMultiThreadModel>{ 
 public:
   CPager(void) {}
   virtual ~CPager(void) {}
 
 BEGIN_COM_MAP(CPager)
   COM_INTERFACE_ENTRY(IMessageSource)
   COM_INTERFACE_ENTRY(IPager2)
   COM_INTERFACE_ENTRY(IPager)
 END_COM_MAP()
 
   STDMETHODIMP GetNextMessage(OLECHAR **ppwsz);
   STDMETHODIMP SendMessage(const COLECHAR * pwsz);
   STDMETHODIMP SendUrgentMessage(void);
 };
前面的代碼產(chǎn)生的接口映射如下:
{ &IID_IMessageSource, 0, _ATL_SIMPLEMAPENTRY },
{ &IID_IPager2, 4, _ATL_SIMPLEMAPENTRY },
  { &IID_IPager, 4, _ATL_SIMPLEMAPENTRY},
  { 0, 0, 0 }
?? 在建立接口映射時(shí),ATL假設(shè)映射中第一個(gè)入口是個(gè)簡(jiǎn)單映射入口并用它來(lái)滿足對(duì)IID_IUnknown.的請(qǐng)求。 除了支持IUnknown外,ATL提供大量缺省的COM接口實(shí)現(xiàn)。ATL用一個(gè)簡(jiǎn)單的命名規(guī)范來(lái)為這些實(shí)現(xiàn)命名,它們大多數(shù)都是作為模板類來(lái)實(shí)現(xiàn)的,帶有一個(gè)模板參數(shù),而這些模板參數(shù)才是是既要實(shí)現(xiàn)的類。 一個(gè)簡(jiǎn)單的例子是IObjectWithSite接口,它一般用于為某個(gè)對(duì)象提供一個(gè)指向激活現(xiàn)場(chǎng)的指針。ATL為這個(gè)指針提供了一個(gè)缺省的實(shí)現(xiàn):IObjectWithSiteImpl。此類提供了一個(gè)IObjectWithSite兼容的二進(jìn)制簽名并且實(shí)現(xiàn)了所有的IObjectWithSite方法。為了使用ATL內(nèi)建的實(shí)現(xiàn),你只需要添加基類實(shí)現(xiàn)(用適當(dāng)?shù)哪0鍏?shù)),然后在接口映射中添加一個(gè)入口輸出經(jīng)過(guò)QueryInterface實(shí)現(xiàn)的接口。 例如,為了使用ATL的IObjectWithSite實(shí)現(xiàn),按照如下步驟來(lái)做:
 class CPager 
  : public CComObjectRootEx,
    public IPager,
    public IObjectWithSiteImpl
  {
 public:
 BEGIN_COM_MAP(CPager)
   COM_INTERFACE_ENTRY(IPager)
   COM_INTERFACE_ENTRY_IMPL(IObjectWithSite)
 END_INTERFACE_MAP()
   STDMETHODIMP SendMessage(const COLECHAR * pwsz);
 };
由于使用了ATL內(nèi)建的實(shí)現(xiàn)類,也就有了COM_INTERFACE_ENTRY_IMPL宏。之所以要用只個(gè)宏是因?yàn)樵S多ATL的缺省實(shí)現(xiàn)不從它們實(shí)現(xiàn)的接口派生。這樣的話就導(dǎo)致標(biāo)準(zhǔn)的COM_ INTERFACE_ENTRY宏返回不正確的偏移量。例如,因?yàn)镃Pager不從IObjectWithSite派生,用于計(jì)算偏移量的強(qiáng)制類型轉(zhuǎn)換就不會(huì)在對(duì)象中反映,而是用起始位置代替。 在這個(gè)例子中,IObjectWithSiteImpl沒(méi)有基類。而是按照在IObjectWithSite中一樣的順序聲明它的虛函數(shù),產(chǎn)生全兼容的vtable(虛表)結(jié)構(gòu)。ATL使用這個(gè)有點(diǎn)不可思議的技術(shù),原因是它允許缺省實(shí)現(xiàn)支持接口的引用計(jì)數(shù),這一點(diǎn)使用常規(guī)多繼承技術(shù)是很難做到的。 IDispatchImpl也是一個(gè)常用的ATL缺省實(shí)現(xiàn)。這個(gè)類實(shí)現(xiàn)用于雙接口的四個(gè)IDispatch方法,由你的類實(shí)現(xiàn)IDispatch::Invoke所不能完成的方法。不像大多數(shù)其它的ATL實(shí)現(xiàn),這個(gè)類實(shí)際上是從一個(gè)COM接口派生的,有幾個(gè)模板參數(shù):
 template <
   class T,            // 雙接口
   const IID* piid,    // 雙接口IID
   const GUID* plibid, // 包含類型庫(kù)TypeLib
   WORD wMajor = 1,    // 類型庫(kù)的版本
   WORD wMinor = 0,    //類型庫(kù)的版本
   class tihclass = CComTypeInfoHolder
 >
 class IDispatchImpl : public T { ... };

假設(shè)兩個(gè)接口是DIPager 和 DIMessageSource。這個(gè)類的使用如下:
class CPager 
 : public CComObjectRootEx<CComMultiThreadModel>,
   public IDispatchImpl<DIMessageSource,
            &IID_DIMessageSource, &LIBID_PagerLib>,
   public IDispatchImpl<DIPager,
            &IID_DIPager, &LIBID_PagerLib>
 {
 public:
 BEGIN_COM_MAP(CPager)
   COM_INTERFACE_ENTRY(DIMessageSource)
   COM_INTERFACE_ENTRY(DIPager)
 // 下一個(gè)接口指定DIPager為缺省 [default]
   COM_INTERFACE_ENTRY2(IDispatch, DIPager)
 END_INTERFACE_MAP()
   STDMETHODIMP SendMessage(BSTR pwsz);
   STDMETHODIMP GetNextMessage(BSTR *ppwsz);
 };
ATL的第一個(gè)版本使用CComDualImpl名字,現(xiàn)在它只是IDispatchImpl預(yù)處理的一個(gè)別名,以便允許1.x版本和2.x版本的工程能在一起編譯。

不要過(guò)分抽象

??? ATL最不直觀的一個(gè)方面是你所定義和實(shí)現(xiàn)的C++類仍然是抽象基類。沒(méi)錯(cuò),在ATL的模板類和宏上辛苦了半天,卻仍然得不到一個(gè)可以實(shí)例化的類。即使你從 CComObjectRootEx 派生,其結(jié)果同從一個(gè)或更多的ATL接口實(shí)現(xiàn)繼承一樣。從技術(shù)上講,你的對(duì)象不提供 IUnknown 三個(gè)核心方法(QueryInterface,AddRef 和 Release)的實(shí)現(xiàn)。如果你檢查現(xiàn)有ATL之前的 COM 實(shí)現(xiàn),如果不是全部,那么也是大多數(shù)的方法實(shí)現(xiàn)并不在乎這個(gè)類是不是被用于COM聚合或tear-off,是不是被用于獨(dú)立的對(duì)象或一個(gè)包含在內(nèi)的數(shù)據(jù)成員,是不是要作為基于堆的對(duì)象或作為全局變量,以及是不是對(duì)象存在時(shí),一直要保持服務(wù)器運(yùn)行。為了允許最大限度的靈活性,所有這些方面分別通過(guò)ATL家族中的十個(gè)類屬的 CComObject 之一來(lái)說(shuō)明。參見下表:

類名服務(wù)器是否加鎖是否代理IUnknown是否刪除對(duì)象備注
CComObjectYesNoYes常規(guī)情況
CComObjectCachedYes(在第二次AddRef之后)NoYes用于通過(guò)內(nèi)部指針控制的對(duì)象
CComObjectNoLockNoNoYes用于不控制服務(wù)器運(yùn)行的對(duì)象
CComObjectGlobalYes(在第一次AddRef之后)NoNo用于全程變量
CComObjectStackNoNoNo用于不能被增加引用計(jì)數(shù)的基于堆棧的變量
CComContainedObjectNoYesNo用于MFC風(fēng)格的嵌套類
CComAggObjectYesYesYes僅用于聚合實(shí)現(xiàn)
CComPolyObjectYesYes(如果聚合)Yes用于聚合/非聚合實(shí)現(xiàn)
CComTearOffObjectNoYes(僅用于QueryInterface)Yes用于每次請(qǐng)求所創(chuàng)建的tear-offs?
CComCachedTearOffObjectNoYes(通過(guò)第二個(gè)IUnknown)Yes用于在第一次請(qǐng)求和緩存時(shí)所創(chuàng)建的tear-offs

??? 每一個(gè) CComObject 類用派生來(lái)提供正確的 QueryInterface,AddRef 和 Release 實(shí)現(xiàn)。如果QueryInterface,AddRef 和 Release的語(yǔ)義正確,則所有 CComObject 類用你的類名作為模板參數(shù)并創(chuàng)建一個(gè)從你的類派生的新類。在這些類中,CComObjectNoLock 是最容易理解的一個(gè)類。請(qǐng)看其代碼:
 template 
 class CComObjectNoLock : public Base {
 public:
         typedef Base _BaseClass;
         CComObjectNoLock(void* = NULL){}
         ~CComObjectNoLock() {m_dwRef = 1L; FinalRelease();}
 
         STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();}
         STDMETHOD_(ULONG, Release)() {
                 ULONG l = InternalRelease();
                 if (l == 0)
                         delete this;
                 return l;
         }
         STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
         {return _InternalQueryInterface(iid, ppvObject);}
 };
 
 template 
 class CComObject : public Base {
 public:
         typedef Base _BaseClass;
         CComObject(void* = NULL) { _Module.Lock(); }
         ~CComObject() {m_dwRef = 1L; FinalRelease(); _Module.Unlock();
         }
 
         STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();}
         STDMETHOD_(ULONG, Release)() {
                 ULONG l = InternalRelease();
                 if (l == 0)
                         delete this;
                 return l;
         }
         STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
         {return _InternalQueryInterface(iid, ppvObject);}
         static HRESULT WINAPI CreateInstance(CComObject** pp);
 };      
      
?? 它假設(shè)你的對(duì)象將在堆中分配,也就是說(shuō)最終調(diào)用 Release 時(shí),將觸發(fā)這個(gè)對(duì)象的 delete 操作。CComObjectNoLock 假設(shè)你的對(duì)象不是可聚合的,并且在服務(wù)器運(yùn)行時(shí),對(duì)象并不一直存在(因此有后綴 NoLock)。
??? 為了在堆中分配基于 ATL 類的 CPager 實(shí)例,只要對(duì)這個(gè)類名進(jìn)行 CComObject 模板包裝即可:
 IPager *p = new CComObjectNoLock();
      
??? CComObjectNoLock 類從 CPager 派生,并且添加了由 CPager 提供的使用 InternalQueryInterface,InternalAddRef 和 InternalRelease 的 QueryInterface,AddRef 和 Release實(shí)現(xiàn)。因?yàn)榇藢?duì)象是基于堆的,所以對(duì) delete 的調(diào)用將發(fā)生在 CComObjectNoLock 類的 Release 實(shí)現(xiàn)中,此時(shí)InternalRelease 返回零。
??? CComObject 類通常被用于基于堆的對(duì)象,這個(gè)對(duì)象只要存在,則服務(wù)器一直要運(yùn)行。與許多 CComObject 家族中的其它類一樣,CComObject提供了一個(gè)全程變量,_Module,它有兩個(gè)方法,Lock 和 Unlock。這些方法與 MFC 的 AfxOleLockApp 和AfxOleUnLockApp 很相似。CComObject 的構(gòu)造函數(shù)調(diào)用_Module 的 Lock 方法,而 CComObject 的析構(gòu)函數(shù)則調(diào)用_Module的Unlock 方法。ATL提供了一個(gè) CComModule 類,它以適當(dāng)?shù)姆绞綖檫M(jìn)程內(nèi)服務(wù)器實(shí)現(xiàn)了這些方法。當(dāng)建立進(jìn)程外服務(wù)器時(shí),某個(gè)派生類(CExeModule)必須改寫默認(rèn)的 Lock / Unlock方法,以便以某種適當(dāng)?shù)姆绞较碌舴?wù)器。基于 AppWizard 的 ATL工程自動(dòng)會(huì)有一個(gè)類用 PostThreadMessage 終止主線程的消息循環(huán)。

輸出你的類

?? 實(shí)現(xiàn)了 CComObject ,你就有足夠的條件用 C++ new 操作符創(chuàng)建 COM 對(duì)象。不過(guò)這樣做沒(méi)有什么實(shí)用價(jià)值,因?yàn)楫吘雇獠靠蛻舳耸褂?CoCreateInstance 或 CoGetClassObject 創(chuàng)建類實(shí)例。也就是說(shuō),你必須為每個(gè)外部類輸出類對(duì)象。幸運(yùn)的是ATL分別在它的 CComClassFactory 和 CComClassFactory2 類中提供了缺省的 IClassFactory 和 IClassFactory2接口實(shí)現(xiàn)。
??? CComClassFactory 不是模板驅(qū)動(dòng)類,但其中有一個(gè)函數(shù)指針作為數(shù)據(jù)成員,使用這個(gè)函數(shù)可以創(chuàng)建對(duì)象。ATL提供了一個(gè)類模板家族,它們都有一個(gè)單獨(dú)的靜態(tài)方法 CreateInstance,由 Creators 調(diào)用,Creators 提供正確的語(yǔ)義來(lái)從 CComClassFactory 創(chuàng)建基于 CComObjectRoot 的對(duì)象。下面的這段代碼展示了缺省的創(chuàng)建機(jī)制:CComCreator,它產(chǎn)生一個(gè)模板化的類實(shí)例,并用 ATL 中標(biāo)準(zhǔn)的 FinalConstruct 來(lái)順序初始化對(duì)象。

 ATL Creator 


 template  class CComCreator {
 public:
     static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) {
                 HRESULT hRes = E_OUTOFMEMORY;
                 T1* p = NULL;
                 ATLTRY(p = new T1(pv))
                 if (p != NULL) {
                         p->SetVoid(pv);
                         p->InternalFinalConstructAddRef();
                         hRes = p->FinalConstruct();
                         p->InternalFinalConstructRelease();
                         if (hRes == S_OK)
                                 hRes = p->QueryInterface(riid, ppv);
                         if (hRes != S_OK)
                                 delete p;
                 }
                 return hRes;
         }
 };
 
 template  class CComFailCreator {
 public:
         static HRESULT WINAPI CreateInstance(void*, REFIID, 
                                              LPVOID*)
     { return hr; }
 };
 
 template  class CComCreator2 {
 public:
         static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, 
                                              LPVOID* ppv) {
                 HRESULT hRes = E_OUTOFMEMORY;
                 if (pv == NULL)
                         hRes = T1::CreateInstance(NULL, riid, ppv);
                 else
                         hRes = T2::CreateInstance(pv, riid, ppv);
                 return hRes;
         }
 };     
      
??? 因?yàn)?ATL 利用 Visual C++ 中的__declspec(novtable) 優(yōu)化,所以在很大程度上依賴兩層構(gòu)造。declspec 取消掉了在抽象基類的構(gòu)造函數(shù)中必須對(duì) vptr 進(jìn)行的初始化,因?yàn)槌橄蠡愔械娜魏蔚?vptr 會(huì)在派生類中被重寫。之所以要進(jìn)行這種優(yōu)化,是因?yàn)槌跏蓟瘡奈幢皇褂眠^(guò)的 vptr 毫無(wú)意義。另外,因?yàn)椴恍枰獮槌橄蠡惙峙鋠table,從而減少了代碼的大小。
??? 使用這種技術(shù)的類(包括大多數(shù) ATL 基類)需要當(dāng)心,不要調(diào)用構(gòu)造器中的虛函數(shù)。但是,為了在初始化時(shí)允許對(duì)虛函數(shù)的調(diào)用,ATL 的 Creators 調(diào)用 FinalConstruct 方法,在這個(gè)方法中進(jìn)行所有重要的初始化工作。在 FinalConstuct 中,從C++的角度看,你的類已經(jīng)完全構(gòu)造好了,也就是說(shuō)你的所有對(duì)象的 vptr 完全被派生化。同時(shí),基于 CComObject 的打包器也同時(shí)構(gòu)造好了,允許你存取在 COM 聚合或 tear-off 情況下無(wú)法知道的控制。
??? 如果在調(diào)試器中單步順序執(zhí)行 Creator 調(diào)用,你將注意到在缺省情況下對(duì) InternalFinalConstructAddRef 和 InternalFinalConstructRelease 的調(diào)用什么也沒(méi)做,但是,如果你打算在你的 FinalConstruct 實(shí)現(xiàn)中創(chuàng)建 COM 聚合,你可能會(huì)臨時(shí)增加一次對(duì)象的引用計(jì)數(shù),以防止它過(guò)早銷毀(這發(fā)生在某個(gè)聚合對(duì)象調(diào)用 QueryInterface時(shí))。你能通過(guò)添加下面的類定義行進(jìn)行自我保護(hù):
 DECLARE_PROTECT_FINAL_CONSTRUCT()
      
這一行代碼重新定義了類的 InternalFinalConstructAddRef 和 InternalFinalConstructRelease 來(lái)增減引用計(jì)數(shù),從而安全地傳遞可能調(diào)用 QueryInterface 的對(duì)象指針。
??? 每一個(gè)基于ATL的工程都包含著一個(gè) CComModule 派生類的實(shí)例。除了實(shí)現(xiàn)前面提到過(guò)的服務(wù)器生命期行為外,CComModule 還維持著一個(gè) CLSID 到 ClassObject 的映射(叫做對(duì)象映射 Object Map)向量來(lái)提供所有外部可創(chuàng)建類。這個(gè)對(duì)象映射被用于實(shí)現(xiàn)進(jìn)程內(nèi)服務(wù)器的 DllGetClassObject,并且它為進(jìn)程外服務(wù)器每次調(diào)用 CoRegisterClassObject 提供參數(shù)。雖然能直接顯式地使用 CComClassFactory 和 Creator 類,但通常都是在 ATL 對(duì)象映射基礎(chǔ)的上下文中使用。 ATL Object Map 是一個(gè)_ATL_OBJMAP_ ENTRY結(jié)構(gòu)數(shù)組:
   
 struct _ATL_OBJMAP_ENTRY {
   const CLSID* pclsid;
   HRESULT (*pfnUpdateRegistry)(BOOL bRegister);
   HRESULT (*pfnGetClassObject)(void* pv, 
                       REFIID riid, LPVOID* ppv);
   HRESULT (*pfnCreateInstance)(void* pv, 
                       REFIID riid, LPVOID* ppv);
   IUnknown* pCF;
   DWORD dwRegister;
   LPCTSTR  (* pfnGetObjectDescription)(void);
 };   
pfnGetClassObject成員的調(diào)用是在第一次需要?jiǎng)?chuàng)建新的類對(duì)象時(shí)。這個(gè)函數(shù)被作為 Creator 函數(shù)(pfnCreateInstance)的第一個(gè)參數(shù)傳遞,并且返回的結(jié)果接口指針被緩存在pCF成員中。通過(guò)按需要?jiǎng)?chuàng)建類對(duì)象,而不是靜態(tài)地實(shí)例化變量,就不再需要使用帶虛函數(shù)的全局對(duì)象,使得基于 ATL 的工程不用C運(yùn)行庫(kù)就能進(jìn)行鏈接。(在 DllMain / WinMain 以前,C運(yùn)行時(shí)必須用來(lái)構(gòu)造全局和靜態(tài)變量。)
??? 雖然你可以顯式地定義用于對(duì)象映射的各種函數(shù),通常的方法是將 CComCoClass 添加到你自己類的基類列表中。CComCoClass 是一個(gè)模板類,它有兩個(gè)模板參數(shù):你自己的類名和對(duì)應(yīng)的 CLSID 指針。它添加適當(dāng)?shù)念愋投x和靜態(tài)成員函數(shù)來(lái)提供對(duì)象映射必須的功能。下面的代碼示范了 CComCoClass 的使用:
    
class CPager 
  : public CComObjectRootEx,
    public CComCoClass,
    public IPager 
 {
 public:
 BEGIN_COM_MAP(CPager)
   COM_INTERFACE_ENTRY(IPager)
 END_INTERFACE_MAP()
   STDMETHODIMP SendMessage(const OLECHAR * pwsz);
 };   
一旦你從CComCoClass派生,你的類就已經(jīng)被添加到ATL Object Map中。ATL所提供的用來(lái)簡(jiǎn)化建立對(duì)象映射的宏很像接口映射宏。下面就是為多CLSID服務(wù)器建立的一個(gè)對(duì)象映射。
BEGIN_OBJECT_MAP(ObjectMap)
   OBJECT_ENTRY(CLSID_Pager, CPager)
   OBJECT_ENTRY(CLSID_Laptop, CLaptop)
 END_OBJECT_MAP()
      
這個(gè)代碼建立了一個(gè)叫 ObjectMap 的 _ATL_OBJMAP_ENTRY 數(shù)組,初始化如下:

?static _ATL_OBJMAP_ENTRY ObjectMap[] = {
?{? &CLSID_Pager, &CPager::UpdateRegistry,?????
??? &CPager::_ClassFactoryCreatorClass::CreateInstance,?
??? &CPager::_CreatorClass::CreateInstance, NULL, 0,?
??? &CPager::GetObjectDescription?
?},
?{? &CLSID_Laptop, &CLaptop::UpdateRegistry,?????
??? &CLaptop::_ClassFactoryCreatorClass::CreateInstance,?
??? &CLaptop::_CreatorClass::CreateInstance, NULL, 0,?
??? &CLaptop::GetObjectDescription?
?},
?{ 0, 0, 0, 0 } };??????

靜態(tài)成員函數(shù)從 CComCoClass 派生,被隱含式定義。以上定義的對(duì)象映射一般通過(guò)使用 CComModule 的 Init 方法被傳遞到ATL:

     
 _Module.Init(ObjectMap, hInstance);
      
這個(gè)方法根據(jù)創(chuàng)建的服務(wù)器類型,在 DllMain 或 WinMain 中被調(diào)用。
??? 缺省情況下,CcomCoClass 為你的類提供了一個(gè)標(biāo)準(zhǔn)的類工廠,允許客戶端聚合你的對(duì)象。你可以通過(guò)添加下面的類定義代碼行來(lái)改變?nèi)笔〉木酆闲袨椋?
     
      DECLARE_NOT_AGGREGATABLE(CPager)
      DECLARE_ONLY_AGGREGATABLE(CPager)
      DECLARE_POLY_AGGREGATABLE(CPager)
      
??? 這些宏只是將 ATL Creator 定義成一個(gè)將被用于初始化對(duì)象映射的嵌套類型(CreatorClass)。前面兩個(gè)宏是自解釋的(它們禁止或需要聚合)。 第三個(gè)宏需要解釋一下。缺省情況下,CComCoClass 使用 ATL 類創(chuàng)建機(jī)制,根據(jù)是否需要使用聚合來(lái)創(chuàng)建兩個(gè)不同的類之一。如果不需要聚合,則創(chuàng)建新的 CComObject 實(shí)例。如果需要聚合,則創(chuàng)建新的CComAggObject實(shí)例。也就是說(shuō)兩個(gè)不同的 vtables 必須在可執(zhí)行文件中出現(xiàn)。對(duì)照之下,DECLARE_POLY_ AGGREGATABLE 總是創(chuàng)建一個(gè) CComPolyObject 實(shí)例,并根據(jù)對(duì)象是否聚合來(lái)初始化這個(gè)外部控制指針。亦即只要定義一個(gè)C++類,只需一個(gè) vtable。這個(gè)技術(shù)的不足之處是:非聚合對(duì)象的每個(gè)實(shí)例必須為非代理 IUnknown 指針多用4個(gè)字節(jié)。不論哪種情況,支持聚合都不需要實(shí)際的編碼,而只是在實(shí)例和代碼大小之間作出取舍。

ATL和注冊(cè)表

??? CComModule 提供了兩個(gè)方法用于自注冊(cè):一個(gè)是RegisterServer,另外一個(gè)是 UnregisterServer。這兩個(gè)方法使用傳遞到 Init 例程的對(duì)象映射來(lái)完成實(shí)際的工作。正像我前面所提到的那樣,每一個(gè)對(duì)象映射入口都包含 pfnUpdateRegistry 函數(shù)指針,這個(gè)指針必須由類實(shí)現(xiàn)者提供。ATL最初的版本所提供的例程為 CLSID 自動(dòng)添加標(biāo)準(zhǔn)注冊(cè)入口,從而使缺省行為的實(shí)現(xiàn)很容易。可惜這些例程不具備很好的可擴(kuò)展性,而且如果服務(wù)器的需求超過(guò)了正常 InprocServer32 入口所包含的內(nèi)容的話,就必須自己用手工來(lái)編寫注冊(cè)代碼。
??? 隨著組件種類(categories)和 AppIDs 概念的出現(xiàn),幾乎就再?zèng)]有服務(wù)器能認(rèn)可由ATL1.0提供的標(biāo)準(zhǔn)注冊(cè)入口。在ATL1.1及以后的版本中,首選的自注冊(cè)技術(shù)是使用注冊(cè)腳本,它非常靈活。這個(gè)技術(shù)需要 IRegistrar 接口的COM實(shí)現(xiàn),它既可以靜態(tài)鏈接以降低依賴性,也可以用 CoCreateInstance 動(dòng)態(tài)綁定來(lái)最小化代碼尺寸。
??? 注冊(cè)腳本只是個(gè)文本文件,它列出必須為給定的 CLSID 添加什么入口。注冊(cè)腳本文件默認(rèn)的擴(kuò)展名為RGS,并作為定制的 REGISTRY 類型資源被添加進(jìn)可執(zhí)行文件。注冊(cè)腳本的語(yǔ)法十分簡(jiǎn)單,歸納起來(lái)為:

 [NoRemove|ForceRemove|val] Name [ = s|d ''''Value'''']?
 {
   ... 用于子鍵的腳本條目
 }
     
NoRemove 前綴表示在進(jìn)行注銷時(shí)不刪除這個(gè)鍵。ForceRemove 前綴表示在寫這個(gè)鍵之前刪除當(dāng)前的鍵和子鍵。Val 前綴表示這個(gè)入口是個(gè)命名的值,而不是一個(gè)鍵。s和d值前綴分別表示REG_SZ 或 REG_DWORD。ATL的解析機(jī)制既可以識(shí)別 HKEY_CLASSES_ROOT 等標(biāo)準(zhǔn)的注冊(cè)表鍵,也能識(shí)別HKCR之類的縮寫表示。
下面是個(gè)腳本注冊(cè)的例子:REGEDIT4
 REGEDIT4
 [HKEY_CLASSES_ROOT\CLSID\{XXX}]
 @=My Class
 [HKEY_CLASSES_ROOT\CLSID\{XXX}\InprocServer32]
 @=C:\foo\bar.dll
 ThreadingModel=Free
其對(duì)應(yīng)的注冊(cè)腳本如下:
 HKCR {
   NoRemove CLSID {
     ForceRemove {XXX} = s ''''My Class'''' {
       InprocServer32 = s ''''%MODULE%'''' {
         val ThreadingModel = s ''''Free''''
       }
     }
   }
 }  
在使用資源腳本的時(shí)候,你的類 UpdateRegistry 方法可以輕松地通過(guò)DECLARE_ REGISTRY_RESOURCEID宏定義,它有一個(gè)資源ID(通常在resource.h中定義)作為參數(shù):
class CPager : public 
 CComObjectRoot,public   
 IPager
   CComCoClass {
   DECLARE_REGISTRY_RESOURCEID(IDR_PAGER)
 };
 
這個(gè)宏僅僅定義了 UpdateRegistry 方法,它調(diào)用內(nèi)建在 CComModule 中的方法UpdateRegistryFromResource。這個(gè)方法有調(diào)用資源腳本的解析機(jī)制。
在上面顯示的注冊(cè)腳本中,所有出現(xiàn) %MODULE% 的地方將被實(shí)際的 GetModuleFileName 調(diào)用結(jié)果所代替。如果你需要根據(jù)動(dòng)態(tài)運(yùn)行值添加額外的注冊(cè)條目,可以添加其它的在注冊(cè)之前能被置換的串,而不是用 %MODULE%。為此,首先要選用一個(gè)新的置換變量,用百分符號(hào)限定變量名。例如:
 DateInstalled = s ''''%CURRENTDATE%'''' 
然后丁一個(gè)定制的UpdateRegistry方法代替使用DECLARE_REGISTRY_ RESOURCEID宏,在你的方法中,建立一個(gè)名字-值對(duì)置換表,提供給模塊的注冊(cè)引擎。
下面是用包含當(dāng)前日期的串來(lái)替換%CURRENTDATE%變量的一個(gè)例子:
static HRESULT WINAPI 
 CPager::UpdateRegistry(BOOL b) {
   OLECHAR wsz [1024]; SYSTEMTIME st;  
   GetLocalTime(&st);
   wsprintfW(wsz, L"%d/%d/%d", st.wMonth, st.wDay, 
             st.wYear);
   _ATL_REGMAP_ENTRY rm[] = {
     { OLESTR("CURRENTDATE"), wsz}, { 0, 0 }, 
   };
   return _Module.UpdateRegistryFromResource(IDR_PAGER,b, rm);
 }
這個(gè)代碼和注冊(cè)腳本最后的運(yùn)行結(jié)果是注冊(cè)鍵 DateInstalled 將包含安裝時(shí)的日期。

連接

??? COM 編程最單調(diào)乏味的一個(gè)方面是使用連接點(diǎn)來(lái)支持 outbound 接口。IConnectionPoint/IConnectionPointContainer 的設(shè)計(jì)好像是專門用來(lái)解決這個(gè)問(wèn)題的,但是經(jīng)驗(yàn)證明,不論是在性能方面,還是在易用性方面,它還是存在不足之處。ATL為每一個(gè)這樣的接口提供了缺省的實(shí)現(xiàn)類,多少解決了一些易用性問(wèn)題。
??? 要理解ATL如何實(shí)現(xiàn)連結(jié)點(diǎn),最容易的方式是看例子:假設(shè)定義了如下的 outbound 接口:

interface IPageSink : IUnknown {
   HRESULT OnPageReceived(void);
 }
 interface IStopSink : IUnknown {
   HRESULT OnShutdown(void);
 }
 
為了支持這兩個(gè) outbound 接口,下面的ATL代碼已經(jīng)足夠:
 class CPager 
 : public CComObjectRoot, 
   public CComCoClass,
   public IPager,
   public IConnectionPointContainerImpl,
   public IConnectionPointImpl,
   public IConnectionPointImpl
 {
 BEGIN_COM_MAP(CPager)
   COM_INTERFACE_ENTRY(IPager)
   COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
 END_COM_MAP()
 
 BEGIN_CONNECTION_POINT_MAP(CPager)
   CONNECTION_POINT_ENTRY(IID_IPageSink)
   CONNECTION_POINT_ENTRY(IID_IStopSink)
 END_CONNECTION_POINT_MAP()
 
 };
 
??? 大多數(shù)有經(jīng)驗(yàn)的 COM 程序員首先注意到的是 CPager 類從一個(gè)接口(IConnectionPoint)派生的,這個(gè)接口并不作為 COM 本身的一部分提供。為了實(shí)現(xiàn)這種訣竅,ATL 類 IConnectionPointImpl 不從接口 IConnectionPoint 派生,而是象 IConnectionPoint 那樣以相同的順序定義它的虛函數(shù)。
??? 其次,為了防止每一個(gè)基類繼承主對(duì)象的 QueryInterface 實(shí)現(xiàn),IConnectionPointImpl中第一個(gè)虛函數(shù)不是QueryInterface。而是一個(gè)類型兼容的方法,它叫做 LocCPQueryInterface,這個(gè)方法只針對(duì) IID_IConnectionPoint 和 IID_IUnknown。它除了涉及允許完全基于多線程實(shí)現(xiàn)外,還有許多深?yuàn)W的竅門在里面。
??? 對(duì)象的 FindConnectionPoint 方法實(shí)現(xiàn)使用由 ATL中 CONNECTION_POINT 宏定義的連接點(diǎn)映射。此映射是對(duì)象中的一個(gè) DWORD表,表示與 IConnectionPoint 實(shí)現(xiàn)相對(duì)應(yīng)的偏移量。FindConnectionPoint 遍歷此偏移量數(shù)組,詢問(wèn)每一個(gè)所碰到的連接點(diǎn),看看此連接是否擁有所請(qǐng)求的接口。
??? 上述例子建立了一個(gè)有效的對(duì)象實(shí)現(xiàn),它支持作為 outbound 接口的 IStopSink 和 IPageSink 。但是,為了調(diào)用outbound 接口,你需要存取由 IConnectionPointImpl 類操縱的接口指針向量,并自己手動(dòng)模擬多點(diǎn)傳送:
  
typedef IConnectionPointImpl base;
 for (IUnknown** pp = base::m_vec.begin();
      pp < base::m_vec.end(); 
      pp++)
   if (*pp) 
    ((IPageSink*)(*pp))->OnPageRecieved();
      
編寫多點(diǎn)傳送例程十分繁瑣。所幸的是,ATL提供了 Visual Studio 組件—— ATL 代理產(chǎn)生器(ATL Proxy Generator),(如下圖五)它們會(huì)讀取接口的類型庫(kù)描述并產(chǎn)生 IConnectionPointImpl 派生類,為每一個(gè) outbound 方法添加適當(dāng)?shù)?Fire 例程。連結(jié)點(diǎn)代理便被產(chǎn)生出來(lái)。

圖五 ATL 代理產(chǎn)生器?
這個(gè)類的定義應(yīng)該像下面這樣:
class CPager 
 : public CComObjectRoot, 
   public CComCoClass,
   public IPager,
   public IConnectionPointContainerImpl,
   public CProxyIPageSink,
   public CProxyIStopSink
 {
 BEGIN_COM_MAP(CPager)
   COM_INTERFACE_ENTRY(IPager)
   COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
 END_COM_MAP()
 
 BEGIN_CONNECTION_POINT_MAP(CPager)
   CONNECTION_POINT_ENTRY(IID_IPageSink)
   CONNECTION_POINT_ENTRY(IID_IStopSink)
 END_CONNECTION_POINT_MAP()
 };
為了發(fā)送出境方法通知,你只要調(diào)用適當(dāng)?shù)腇ire_XXX方法即可:
 STDMETHODIMP CPager::SendMessage(LPCOLESTR pwsz) {
 // send outbound notifications
   HRESULT hr = Fire_OnPageRecieved();
 // process normally
   return hr;
 }
??? 機(jī)器產(chǎn)生代理的一個(gè)限制是必須要 outbound 接口的類型庫(kù)定義。對(duì)于大量的 COM 接口而言,這是不可能的,因?yàn)轭愋蛶?kù)在轉(zhuǎn)換方面與 IDL 特性背道而馳。對(duì)于更復(fù)雜的接口,你可以從機(jī)器所產(chǎn)生的代理開始并修改這些代碼來(lái)進(jìn)行嘗試。

后記

??? 本文概括地討論了 ATL 核心體系結(jié)構(gòu),主要針對(duì) ATL 中使用,同時(shí)也是 ATL 用戶使用的基本編程風(fēng)格。實(shí)際上本文中所討論的和涉及的內(nèi)容特性都是基于 ATL1.1 版本的,某些內(nèi)容在 ATL2.0 中稍有改動(dòng)。本文沒(méi)有包含有關(guān)ActiveX 控件接口的缺省實(shí)現(xiàn)方面的內(nèi)容,它們被加到了 ATL2.0 版本中,遵循與 ATL1.1 同樣的編程哲學(xué)。只要你常常推敲 ATL 源代碼,這些接口會(huì)很容易理解。
??? 在與同事和朋友討論 ATL 時(shí),我發(fā)現(xiàn)大多數(shù)人的感觸是針對(duì) ATL 的設(shè)計(jì)。有一小部分人覺(jué)得它非常棒。另外一小部分人覺(jué)得它復(fù)雜而不可思議。但大多數(shù)人(包括我自己)的感覺(jué)是雙重的,優(yōu)劣兼而有之。幸運(yùn)的是 ATL 編程是一個(gè)非常"量入為出"的過(guò)程。只要你去學(xué)總會(huì)有收獲,所以初學(xué)者需要努力而為之。

From:http://www.vckbase.com/
翻譯者:趙湘寧
posted on 2007-01-29 09:53 我風(fēng) 閱讀(3549) 評(píng)論(0)  編輯 收藏 引用

只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


<2010年10月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

常用鏈接

留言簿(12)

隨筆分類

隨筆檔案

文章檔案

相冊(cè)

收藏夾

C++

MyFavorite

搜索

  •  

積分與排名

  • 積分 - 329000
  • 排名 - 75

最新評(píng)論

閱讀排行榜

評(píng)論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产精品久在线观看| 亚洲第一级黄色片| 欧美日韩一区二区在线播放| 久久一区二区三区av| 久久精品麻豆| 女人香蕉久久**毛片精品| 牛夜精品久久久久久久99黑人 | 国产欧美日韩精品专区| 国产精品区免费视频| 国产欧美日韩一级| 精品999在线观看| 亚洲在线免费观看| 亚洲一区综合| 久久久久国产精品人| 老司机久久99久久精品播放免费| 男男成人高潮片免费网站| 欧美激情四色| 国产精品视频福利| 亚洲国产天堂久久综合网| 一本色道久久综合狠狠躁的推荐| 午夜精品短视频| 免费成人在线观看视频| 亚洲精品人人| 久久久久久久一区二区| 欧美午夜免费影院| 影音先锋久久久| 久久尤物电影视频在线观看| 欧美激情小视频| 国产手机视频精品| 99视频精品全部免费在线| 欧美一区二区三区视频免费| 欧美激情精品久久久久久变态| 亚洲一区二区影院| 欧美日韩成人综合在线一区二区| 狠狠色狠狠色综合系列| 亚洲一区免费看| 亚洲高清av在线| 欧美一区二区三区在线视频 | 夜夜嗨av一区二区三区免费区| 午夜亚洲性色视频| 欧美精品电影| 亚洲第一精品夜夜躁人人爽| 欧美在线资源| 亚洲欧美日韩精品久久奇米色影视| 免费永久网站黄欧美| 精久久久久久久久久久| 欧美专区中文字幕| 亚洲午夜精品网| 欧美日韩久久久久久| 亚洲精品一区二区三区av| 蜜臀av性久久久久蜜臀aⅴ| 午夜精品久久久久久久| 欧美精品www在线观看| 亚洲国产欧美一区二区三区同亚洲| 久久精品欧洲| 欧美亚洲免费高清在线观看| 国产精品人成在线观看免费 | 精品福利电影| 国产日本欧洲亚洲| 亚洲视频网在线直播| 亚洲电影在线免费观看| 欧美尤物巨大精品爽| 国产精品永久免费| 亚洲一二区在线| 99视频+国产日韩欧美| 欧美区在线播放| 欧美成人在线免费视频| 黄色亚洲大片免费在线观看| 欧美在线观看一区| 欧美一区二区三区四区视频| 国产丝袜一区二区| 欧美视频一区二区三区…| 亚洲国产一二三| 亚洲国产另类久久久精品极度| 欧美精品一卡| 欧美一区二区视频在线观看| 日韩视频在线观看免费| 久久激情网站| 136国产福利精品导航| 免费成人av资源网| 欧美成人一区在线| 亚洲无线视频| 香蕉久久精品日日躁夜夜躁| 精品动漫3d一区二区三区免费版| 欧美国产日韩一区| 欧美喷潮久久久xxxxx| 午夜视频在线观看一区| 久久国产日韩| 一区二区三区国产精华| 午夜精品影院| 亚洲国产91色在线| 一区二区三区精品视频| 国产一区亚洲| 亚洲美女视频网| 国产亚洲欧美在线| 亚洲国产欧美日韩精品| 国产精品综合| 亚洲精品久久| 在线欧美日韩精品| 亚洲在线不卡| 99热在这里有精品免费| 欧美在线视频导航| 亚洲视频中文| 欧美不卡在线视频| 久久久久免费| 国产精品久久国产愉拍| 国产九九视频一区二区三区| 快播亚洲色图| 国产美女精品视频| 亚洲电影免费观看高清完整版| 国产精品地址| 亚洲免费成人| 亚洲人成人99网站| 久久九九国产精品| 午夜精品福利电影| 欧美日韩精品三区| 欧美岛国在线观看| 亚洲国产欧美在线人成| 久久久久久久久久久成人| 亚洲精品国精品久久99热一| 亚洲午夜久久久| 一区二区三区欧美视频| 久久露脸国产精品| 久久精品视频在线免费观看| 亚洲国产欧美一区| 韩日在线一区| 午夜免费电影一区在线观看| 亚洲午夜精品福利| 欧美精品在线观看| 亚洲福利av| 亚洲国产乱码最新视频| 久久福利视频导航| 久久精品亚洲精品| 亚洲国产精品一区| 国产免费成人| 亚洲亚洲精品三区日韩精品在线视频| 亚洲精品免费在线| 欧美国产日本在线| 亚洲精品女人| 亚洲精品乱码久久久久久蜜桃麻豆| 久久精品视频免费播放| 久久夜色精品国产欧美乱极品| 国产一区二区三区久久久| 欧美一区二区三区在线视频| 老司机凹凸av亚洲导航| 国内精品99| 久久亚洲私人国产精品va| 欧美成人按摩| 亚洲肉体裸体xxxx137| 欧美日韩国产三级| 亚洲午夜极品| 久久综合九色综合欧美狠狠| 亚洲国产你懂的| 欧美另类videos死尸| 亚洲视频一区在线观看| 久久爱www久久做| 亚洲国产精品成人一区二区 | 亚洲视频在线观看三级| 校园激情久久| 娇妻被交换粗又大又硬视频欧美| 老司机午夜免费精品视频| 亚洲日韩中文字幕在线播放| 亚洲伊人一本大道中文字幕| 国产一级久久| 欧美激情五月| 久久不射中文字幕| 亚洲日本激情| 久久精品亚洲一区| 亚洲久久在线| 国产中文一区| 欧美日韩一区二区三区免费看| 午夜精品短视频| 亚洲欧洲日产国产网站| 欧美一区二区精美| 亚洲二区在线观看| 国产精品入口| 另类天堂av| 亚洲欧美一区二区三区在线| 亚洲成在人线av| 欧美在线影院在线视频| 在线一区二区三区四区五区| 国产综合在线看| 欧美系列一区| 欧美国产专区| 久久国产精品亚洲77777| 一本一本久久a久久精品综合麻豆 一本一本久久a久久精品牛牛影视 | 在线观看91精品国产麻豆| 欧美日韩福利| 久久国产精品久久久久久久久久| 中文高清一区| 亚洲国产岛国毛片在线| 国产精品成人久久久久| 女同性一区二区三区人了人一| 亚洲免费在线电影| 亚洲欧洲午夜| 欧美黄色aaaa| 欧美不卡视频一区发布| 久久精品视频导航| 亚洲欧美伊人| 欧美一级淫片aaaaaaa视频|