程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 組件對象模型的規則

組件對象模型的規則

編輯:關於C++

摘要

本文的目的是為使用和實行Microsoft的組件對象模型(COM)提供迅捷的參考。讀者若想更好的理解什麼是COM,以及隱藏在它的設計及體系中的動機,應該閱讀組件對象模型的技術說明書(MSDN庫,技術說明書)。

規則1:必須實現Iunknown

如果一個對象沒有至少實現一個最小程度為IUnknown的接口,那它就不是Microsoft的組件對象模型(COM)。

接口設計規則

接口必須直接或間接地從IUnknown繼承。

接口必須有唯一的識別(IID)。

接口是不變的。一旦分配和公布了IID,接口定義的任何因素都不能被改變。

接口的成員函數應該有HRESULT類型的返回值,使遠端結構可報告遠程過程調用(RPC)錯誤的情況。

接口成員函數的字符串參數應該是Unicode。

實現 IUnknown

對象的同一性。這要求對任何特定IUnknown接口的給定對象實例的QueryInterface調用返回相同的物理指針變量。這導致了所謂的兩個接口的QueryInterface(IID_IUnknown, ...)和結果的比較,以確定它們是否為同一對象(COM對象同一性)。

靜態接口的設置。任何經由QueryInterface來訪問對象的接口的設置,必須是靜態而不是動態的。也就是說,假如一旦QueryInterface獲得了一個給定的IID,那麼它總是對相同的對象(除非有意想不到情況)調用,假如QueryInterface不能獲得一個給定的IID,那麼隨後對相同IID的對象調用必定會失敗。

對象完整性。對於可處理的接口設置,必須有反身性,對稱性和過渡性。即給定代碼如下:

IA * pA = (some function returning an IA*);
IB * pB = NULL;
HRESULT hr;
hr = pA->QueryInterface(IID_IB,&pB); // line 4
Symmetric: pA->QueryInterface(IID_IA, ...) must succeed (a>>a)
Reflexive: If, in line 4, pB was successfully obtained, then
pB->QueryInterface(IID_IA, ...)
must succeed (a>>b, then b>>a).
Transitive: If, in line 4, pB was successfully obtained, and we do
IC * pC = NULL;
hr = pB->QueryInterface(IID_IC, &pC); //Line 7
and pC is successfully obtained in line 7,then
pA->QueryInterface(IID_IC, ...)
must succeed (a>>b, and b>>c,then a>>c).

最小參考服務大小。我們需要實現AddRef來維護一個服務台,它足夠大以便支持給定對象的所有接口的2 31 –1有出色的整體指示服務。一個32-位的無符號整型數滿足要求。

Release並不意味著失敗。假如客戶想知道關於資源已被釋放等情況,就必須在調用Release之前使用一些對象接口中的較高的語義。

內存管理規則

接口指針的生命期管理總是通過建立在每個COM接口上的AddRef和Release方法來實現。(參見下面的“引用計數規則”)

下面的規則適用於接口成員函數的參數,包括不是“按值”傳遞的返回值。

對於參數來說,調用程序應分配和釋放內存。

出口參數必須由被調用程序分配,由調用程序用標准的COM內存分配程序來釋放。

出入參數首先由調用程序分配,必要時由被調用程序釋放及重分配。至於出口參數,調用程序有責任釋放最終返回變量。此時必須使用標准的COM內存分配程序。

假如函數返回調用失敗的代碼,則通常調用者沒辦法清除出口和入出口參數。這導致了一些附加規則:

錯誤返回時,出口參數必須可靠地被設置成可清除變量,它不能對調用程序有影響。

此外,所有的出口指針參數(包括調用分配,被調用委任結構)必須被明顯地設為NULL。最直接的方法是在函數說明項中設成NULL。

返回錯誤時,所有的入出口參數必須為被調用者所擱置(這樣保持為調用程序初始化的值;若調用程序沒有對它初始化,則它是個出口參數,不是入出口參數),或者被明顯地設為出口錯誤返回情況。

參考計數規則:

規則1:對於接口指針的每一個新的副本,AddRef必須被調用;Release在接口指針的每一個破壞時調用,除了子規則明顯允許了其他情況。

以下規則對應於規則1的非例外情況。

規則1a:函數的入口出口參數。調用程序必須AddRef實際參數,因為當出口變量存放在它之上時,將由被調程序釋放。

規則1b:獲取全局變量。從全局變量的已存在的指針副本得到的接口指針的局部副本,必須被獨立地引用計數。因為存在局部副本時,被調函數會破壞全局副本。

規則1c:新指針合成所需資源不多。函數使用內在知識合成接口指針,而不是從其他資源所得,此時必須對新指針做初始AddRef。這樣的重要例子有事例生成法則,Iunknown::QueryInterface的實現,等等。

規則1d:內部存儲指針副本的返回。指針返回之後,被調程序不知道它的生命期和指針的內部存儲副本如何聯系。所以,被調程序必須在返回前對指針副本調用AddRef。

規則2:對於接口指針的兩個或更多的副本,它們的生命期的起始和終了的關系代碼的特定知識,使AddRef/Release可以被省略。

從COM客戶的角度,引用計數是和接口對應的概念。客戶不應認為對象的所有接口有同一引用計數。

不應依賴於Addref & Release的返回值,而應用於調試目的。

指針穩定性;參見在"Reference-Counting Rules"下的OLE幫助文件中的子部分:"Stabilizing the this Pointer and Keeping it Valid"。

參見Douglas Hodges寫的優秀的技術文章"Managing Object Lifetimes in OLE",及Kraig Brockschmidt (MSDN Library, Books)寫的Inside OLE的第三章來獲取更多信息。

COM申請責任:

以客戶,服務器,對象執行者之一身份使用COM的每一進程,要對三件事負責:

確定COM庫是同COM函數CoBuildVersion一致的版本。

在使用其他函數之前通過調用CoInitialize初始化COM庫。

在不用CoUninitialize時取消COM庫的初始化。

進程內服務器能假定載入的進程已執行了這些步驟。

服務器規則

進程內服務器必須輸出DllGetClassObject and DllCanUnloadNow。

進程內服務器必須支持COM自注冊。

進程內和局部服務器應該在它們的文件版本信息中提供OLESelfReg字符串。

進程內服務器必須輸出DllRegisterServer and DllUnRegisterServer。

局部服務器應支持/RegServer and /UnRegServer命令行開關。

生成集合對象

生成可合計的對象是可選的,且操作簡單,有諸多益處。以下規則使用於創建可合計的對象(通常稱為內部對象)。

由QueryInterface, AddRef, 和 Release對IUnknown接口的內部對象執行單獨控制內部接口的引用計數,且不能授權給外部未知指針。這種IUnknown執行稱為隱式IUnknown。

內部對象執行接口的QueryInterface, AddRef, 和 Release成員的實行,除了IUnknown自己,都必須授權給外部未知指針。這些實施不能直接影響內部對象的參考計數。

隱式Iunknown只對內部對象實施QueryInterface操作。

集合對象在占用外部未知指針參考時,不能調用AddRef。

如果當對象創建時,需要除Iunknown外的任一接口,創建失敗同E_UBKNOWN一起。

以下的代碼段闡明了使用嵌套類來實現集合對象接口的范例:

// CSomeObject is an aggregatable object that implements
// IUnknown and ISomeInterface
class CSomeObject : public IUnknown
{
private:
DWORD m_cRef;// Object reference count
IUnknown* m_pUnkOuter; //Outer unknown, no AddRef
// Nested class to implement the ISomeInterface interface
class CImpSomeInterface: public ISomeInterface
{
friend class CSomeObject ;
private:
private:DWORD m_cRef; //Interface ref-count, for debugging
private:IUnknown*m_pUnkOuter; // Outerunknown, for delegation
private:public:
private:CImpSomeInterface() { m_cRef = 0; };
private:~ CImpSomeInterface(void) {};
private:// IUnknown members delegate to the outer unknown
private:// IUnknown members do not control lifetime of object
private:STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
private:{ return m_pUnkOuter->QueryInterface(riid,ppv);};
private:STDMETHODIMP_(DWORD) AddRef(void)
private:{ return m_pUnkOuter->AddRef(); };
private:STDMETHODIMP_(DWORD) Release(void)
private:{ return m_pUnkOuter->Release();};
private:// ISomeInterface members
private:STDMETHODIMP SomeMethod(void)
private:{ return S_OK; };
private:} ;
private:CImpSomeInterface m_ImpSomeInterface ;
private:public:
private:CSomeObject(IUnknown * pUnkOuter)
{
m_cRef=0;
// No AddRef necessary if non-NULL as we're aggregated.
m_pUnkOuter=pUnkOuter;
m_ImpSomeInterface.m_pUnkOuter=pUnkOuter;
} ;
// Static member function for creating new instances (don't use
// new directly).Protects against outer objects asking for interfaces
// other than IUnknown
static HRESULT Create(IUnknown* pUnkOuter, REFIID riid, void **ppv)
{
CSomeObject* pObj;
if (pUnkOuter != NULL && riid != IID_IUnknown)
return CLASS_E_NOAGGREGATION;
pObj = new CSomeObject(pUnkOuter);
if (pObj == NULL)
return E_OUTOFMEMORY;
// Set up the right unknown for delegation (the non-aggregation
case)
if (pUnkOuter == NULL)
pObj->m_pUnkOuter = (IUnknown*)pObj ;
HRESULT hr;
if (FAILED(hr = pObj->QueryInterface(riid, (void**)ppv)))
delete pObj ;
return hr;
}
// Implicit IUnknown members, non-delegating
// Implicit QueryInterface only controls inner object
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
*ppv=NULL;
if (riid == IID_IUnknown)
*ppv=this;
if (riid == IID_ISomeInterface)
*ppv=&m_ImpSomeInterface;
if (NULL==*ppv)
return ResultFromScode(E_NOINTERFACE);
((IUnknown*)*ppv)->AddRef();
return NOERROR;
} ;
STDMETHODIMP_(DWORD)AddRef(void)
{ return++m_cRef; };
STDMETHODIMP_(DWORD)Release(void)
{
if (--m_cRef != 0)
return m_cRef;
delete this;
return 0;
};
};

集合對象

當在一個對象上產生另一個集合對象時,必須遵循以下規則:

當創建一個內部對象時,外部對象必須明確的向Iunknown請求。

外部對象必須保護重入時對破壞代碼的人工引用的Release實施。

如果外部對象查詢任一內部對象接口,它必須調用自己的未知Release。釋放此指針時,外部對象緊隨內部對象指針調用自己的外部未知AddRef。

// Obtaining inner object interface pointer
pUnkInner->QueryInterface(IID_IFoo,&pIFoo);
pUnkOuter->Release();
// Releasing inner object interface pointer
pUnkOuter->AddRef();
pIFoo->Release();

外部對象不能盲目地對內部對象的未被識別接口進行查詢,除非操作是為外部對象特定目的。

房間線程化模型

房間模型線程化的細節實際上非常簡單,但是必須小心地遵循以下規則:

每個對象存在於單線程中(在單獨的房間中)。

所有對一對象的調用必須基於自己的線程(在自己的房間中)。直接從別的線程中調用對象是禁止的。試圖用空線程方式使用對象的申請,將在操作系統的未來版本中遇上不能正確運行的問題。這條規則的含義就是在房間之間,必須安排對象的所有指針。

為了處理從不同進程或同一進程的不同房間中的調用,在對象中的每一房間/線程必須有一個消息隊列。這就意味著線程的工作函數必須有一個GetMessage/DispatchMessage循環。假如在線程之間有別的同步原語用來通信,那麼Microsoft Win32的sgWaitForMultipleObjects將被用來等待消息和線程同步事件。

基於DLL或進程內的對象必須在注冊表中標記為"房間識別",通過給注冊數據庫的InprocServer32關鍵字增添名為"ThreadingModel=Apartment"的變量實現。

房間識別對象應仔細填寫DLL表項。對房間識別對象調用CoCreateInstance的每一個房間將從它的線程調用DllGetClassObject。故DllGetClassObject應能多級類對象或單線程安全對象。從任一線程調用CoFreeUnusedLibraries,都通過主房間線程來調用DllCanUnloadNow.。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved