Loading AI tools
来自维基百科,自由的百科全书
元件物件模型(英語:Component Object Model,縮寫COM)是微軟的一套軟體元件的二進制介面標準。這使得跨程式語言的行程間通訊、動態物件建立成為可能。COM是多項微軟技術與框架的基礎,包括OLE、OLE自動化、ActiveX、COM+、DCOM、Windows shell、DirectX、Windows Runtime。COM與實作語言種類無關,如此使用它實作的物件可用在不同於開發它的環境,甚至跨越機器邊界。對製作良好的物件,COM使物件得以重複使用,而無須知道其內部實作,因為它強制實作者提供與實作分離、確切定義的介面。各語言不同的儲存組態語意使元件物件模型用物件參照計數(Reference counting)管理其自身的產生與銷毀。不同介面間型別轉換的鑄型用 QueryInterface 方法。
COM的核心是一組元件物件間互動的規範,定義了元件物件如何與其使用者通過二進制介面標準進行互動,COM的介面是元件的類型紐帶。
除了規範之外,COM還是一個稱為「COM媒體櫃」的實現,包括若干API函式,用於COM程式的建立與使用。
COM還提供定位服務的實現,可以根據Windows系統登錄檔,從一個類標識(CLSID)來確定元件的位置。
COM採用自己的IDL來描述元件的介面(interface),支援多介面,解決版本相容問題。COM為所有元件定義了一個共同的父介面IUnknown。GUID 是一個 128 位整數(16 位元組),COM將其用於電腦和網路的唯一識別碼。
除了基本規範和系統實現之外,COM的構成還包括永久儲存、綽號(moniker智慧型命名/標記)和統一資料轉移(UDT = Uniform Data Transfer)三個核心的作業系統部件。
COM實質上是一種語言無關的物件實現方式,這使其可以在建立環境不同的場合、甚至跨電腦的分布環境下被復用。COM允許復用這些物件,而不必知道物件內部是如何實現,因為元件實現者必須提供良好定義的介面從而封鎖實現細節。通過參照計數,元件物件自己負責動態建立與銷毀,從而封鎖了不同程式語言之間的主記憶體分配語意差異。在COM介面之間的類型轉換通過QueryInterface方法。
對於某些應用程式來說,COM已經部份被.NET框架取代。COM通過Windows Communication Foundation(WCF)支援Web Service。COM物件通過.NET COM Interop可以被所有.NET語言使用。網路化的DCOM使用二進制私有格式,而WCF鼓勵使用基於XML的SOAP訊息機制。COM非常類似其他軟體元件介面技術,如CORBA與JavaBeans,它們各自有其優點與弱點。
與C++不同,COM提供了一個穩定的套用二進制介面(ABI),不隨編譯器版本而改變 。
早在1988年,微軟的Anthony Williams的論文「Object Architecture: Dealing with the Unknown or Type Safety in a Dynamically Extensible Class」以及1990年的「On Inheritance: What It Means and How To Use it」論文奠定了COM的理論基礎。[3]
Windows作業系統提供了三種處理程序間的通訊機制:剪貼簿、DDE與OLE。OLE原名是物件連結與嵌入(Object Linking and Embedding),OLE可以說是DDE的改良版。1992年,OLE 1.0版隨Windows 3.1作業系統發布,提供複合文件(compound document)處理,但它過於複雜,Brockschmidt, Kraig的「Inside OLE」一書中提到,必須經過六個月的心靈混沌期,才能了解OLE是什麼。1993年,COM架構隨OLE 2.0第一次公開發布。在微軟Office套件中,COM取代了OLE。這成為COM技術戰勝Windows 95團隊開發的其他物件技術的關鍵因素。
1996年,為應對CORBA,DCOM隨Windows NT 4 Option Pack發布。
1999年,Windows 2000發布了COM+,關注MTS,並放棄了DCOM這個名稱。
COM是基於元件物件方式概念來設計的,在基礎中,至少要讓每個元件都可以支援二個功能:
這二個功能即為COM的根:IUnknown
介面所提供的IUnknown::QueryInterface()
,IUnknown::AddRef()
及IUnknown::Release()
三個方法的由來。所有的COM元件都要實作IUnknown
,表示每個COM元件都有相同的能力。
只由COM衍生實作出來的元件,稱為純COM元件。
但在Windows持續發展時,Visual Basic 4.0開始支援OCX,也就是OLE Custom Control,這讓微軟開始思考要如何讓COM元件可以跨語言支援,在這樣的要求下,必須要提供一個一致的介面,以及提供一組可以呼叫介面內方法的能力,由於純COM元件只能夠支援C/C++的直接存取,為了要達到跨語言的能力,在COM中必須要支援在外部呼叫內部方法的機能,這個機能造就了Invoke()
方法,另外為了跨語言的支援,COM應該要提供簡單的元件存取識別方式,這也就是會有GetIDsOfNames()
的原因,將這些方法組合起來,定義出的必要介面,稱為IDispatch
介面,所有實作此介面的,都可以支援跨語言的支援。
微軟將實作此介面的元件都稱為自動化(Automation)元件。
COM曾是Windows平台下主要的軟體開發平台,並且影響至其他許多相關軟體技術。
COM+是微軟Windows 2000中,Microsoft Transaction Server的強化實作版本,除了提供基本的元件交易支援外,還提供了鬆散藕合式事件(loosely-coupled events)與物件共享池(object pooling)等應用程式伺服器的能力,成為Windows 2000開始在微軟平台上主要的應用程式伺服器平台,目前.NET Framework也提供了System.EnterpriseServices命名空間以支援COM+。
Distributed COM是依據遠程過程調用(RPC,Remote Procedure Call)的規範發展的可以在網路上通訊的COM元件,它將COM元件的能力擴及到網路上,但因為網路安全以及防火牆因素,DCOM無法廣泛的流行。
.NET Framework是新一代的Microsoft Windows應用程式開發平台。使用C#開發COM組件,首先建立類型為Class Library的專案,然後在專案的Property中進入Build頁,對「Register for COM interop」選項打勾。打開AssemblyInfo.cs檔案,設定[assembly: ComVisible(true)],這樣就可以生成.tlb檔案。源程式範例如下:
using System.Runtime.InteropServices;
namespace MyNameSpace
{
//可以通过//菜单“工具/guid 生成”。
[Guid("298D881C-E2A3-4638-B872-73EADE25511C")]
public interface AddComInterface
{
[DispId(1)]
int iadd(int a, int b);
[DispId(2)]
string stradd(string strA, string strB);
}
[Guid("2C5B7580-4038-4d90-BABD-8B83FCE5A467")]
[ClassInterface(ClassInterfaceType.None)]
public class AddComService : AddComInterface
{
public AddComService()
{
}
public int iadd(int a, int b)
{
int c = a + b;
return c;
}
public string stradd(string strA, string strB)
{
return strA+strB ;
}
}
}
不同的COM元件類型用類ID(CLSID)標示,這是一種全域唯一識別碼(GUID)。每個COM元件用一個或多個介面來暴露其功能。這些介面也採用GUID唯一標識,稱為介面ID(IID)。
COM介面與幾種程式語言有語言繫結,如C語言、C++、Visual Basic、Delphi語言、Python[4][5]以及Windows平台上的幾種手稿語言。它們都是通過介面的方法來訪問元件。
所有COM元件都實現了IUnknown介面,該介面暴露了參照計數實現的物件生命期管理與類型轉換,以訪問不同的預定義介面。
IUnknown介面以及基於IUnknown的客製化介面包括一個指向虛擬函式表的指標,虛擬函式表中包含若干函式指標,分別指向介面所聲明的函式實現。對於處理程序內的COM元件呼叫,其效率等同於C++的虛擬函式呼叫。
除了基於IUnknown的客製化介面,COM也支援繼承自IDispatch的dispatch介面,從而支援了用於OLE自動化的晚繫結。不能訪問客製化介面的程式語言(例如VBS)可以通過dispatch介面訪問COM元件。
Windows API提供了C語言定義COM介面的方法:
#include <objbase.h>
#undef INTERFACE
#define INTERFACE IClassFactory
DECLARE_INTERFACE_(IClassFactory, IUnknown)
{
// *** IUnknown methods ***
STDMETHOD(QueryInterface) (THIS_
REFIID riid,
LPVOID FAR* ppvObj) PURE;
STDMETHOD_(ULONG,AddRef) (THIS) PURE;
STDMETHOD_(ULONG,Release) (THIS) PURE;
// *** IClassFactory methods ***
STDMETHOD(CreateInstance) (THIS_
LPUNKNOWN pUnkOuter,
REFIID riid,
LPVOID FAR* ppvObject) PURE;
};
// 等效的C++例子:
struct FAR IClassFactory : public IUnknown
{
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
IID FAR& riid,
LPVOID FAR* ppvObj) = 0;
virtual HRESULT STDMETHODCALLTYPE AddRef(void) = 0;
virtual HRESULT STDMETHODCALLTYPE Release(void) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateInstance(
LPUNKNOWN pUnkOuter,
IID FAR& riid,
LPVOID FAR* ppvObject) = 0;
};
// C语言宏扩展后是这样的:
typedef struct IClassFactory
{
const struct IClassFactoryVtbl FAR* lpVtbl;
} IClassFactory;
typedef struct IClassFactoryVtbl IClassFactoryVtbl;
struct IClassFactoryVtbl
{
HRESULT (STDMETHODCALLTYPE * QueryInterface) (
IClassFactory FAR* This,
IID FAR* riid,
LPVOID FAR* ppvObj) ;
HRESULT (STDMETHODCALLTYPE * AddRef) (IClassFactory FAR* This) ;
HRESULT (STDMETHODCALLTYPE * Release) (IClassFactory FAR* This) ;
HRESULT (STDMETHODCALLTYPE * CreateInstance) (
IClassFactory FAR* This,
LPUNKNOWN pUnkOuter,
IID FAR* riid,
LPVOID FAR* ppvObject);
HRESULT (STDMETHODCALLTYPE * LockServer) (
IClassFactory FAR* This,
BOOL fLock);
};
COM類(coclass)是一個或多個介面的具體實現,它很類似物件導向程式設計語言中的類。類的GUID標識被稱作類ID(CLSID);或者programmatic identifier字串(progid),因為VBS等手稿語言不能使用GUID,只能用字串尋找、使用COM元件。
COM物件不能被直接訪問,只能通過COM介面來訪問物件。COM也支援同一個介面的多個實現,因此客戶程式執行時可以選擇實例化介面的哪個實現。
類型媒體櫃(type library)包含著COM類型的元資料。這些類型採用微軟介面定義語言(MIDL)描述。
IDL檔案定義了類、介面、結構、列舉與其他使用者定義類型。IDL類似於C++的聲明,使用了一些額外的關鍵字如interface、library等。IDL還支援在聲明前給出方括號內容(bracketed attribute)以提供額外資訊,如介面的GUID、指標參數與長度域之間的關係等。
MIDL編譯器用來編譯IDL檔案,產生編譯器獨立的標頭檔。標頭檔包含了IDL檔案中聲明的介面對應的結構定義。結構只包含一項成員,即指向在介面中聲明函式的位址表的指標(vtbl),以模仿C++對虛擬函式的實現。標頭檔還包含了類與介面等的GUID的常數的定義。MIDL編譯器也可以產生C++原始檔,包含代理模組(proxy module),用以把COM呼叫轉為遠端程序呼叫,以支援跨處理程序的DCOM通訊。
IDL檔案也可以被MIDL編譯器生成類型媒體櫃(TLB)檔案.TLB
,以供其他語言編譯器與執行時環境使用,如VB、Delphi、.NET等生成語言相關表示COM類型的結構。C++把TLB轉回到IDL表示。
使用C++的預編譯directive#import
,可以裝入如下格式的類型資訊:
#import
建立兩個標頭檔以用C++原始碼形式恢復類型媒體櫃資訊:
兩個檔案被放在輸出目錄中。編譯器在現場就地#include
主標頭檔。
類型媒體櫃主標頭檔(.TLH)包含七部份:
_com_ptr_t
的範本特化。#include
類型媒體櫃次標頭檔#pragma pack(pop)
從第2至第6部份都包含在命名空間中,其名字在最初的IDL檔案的library
語句中給出。改名字在#import
語句中可用內容no_namespace抑制掉;也可用rename_namespace內容更名。
COM是一個執行時框架,類型必須在執行時單獨地標識並可指定。為此,使用GUID,每個COM類型被指定了它自己的GUID用於執行時標識。這也解決了C/C++語言的名字修飾導致的連結相容性問題。
為了使COM類型資訊在編譯時與執行時都可以訪問,COM使用類型媒體櫃。這使得COM成為物件互動的動態框架。
考慮下述用IDL定義coclass的例子:
coclass SomeClass {
[default] interface ISomeInterface;
};
上述程式碼框架聲明了一個COM類,稱為SomeClass
,實現了介面ISomeInterface
。
這在概念是等價於下述C++類別:
class SomeClass : public ISomeInterface {
...
...
};
其中ISomeInterface是一個C++虛基礎類別。
包含COM介面與類的IDL檔案被編譯為類型媒體櫃(TLB)檔案。客戶程式可以在執行時分析類型媒體櫃檔案,以確定物件支援哪些介面,然後呼叫物件的介面方法。
C/C++程式以類ID(CLSID)與介面ID(IID)作為參數,用CoCreateInstance
函式實例化COM物件。SomeClass
的實例化程式碼如下:
ISomeInterface* interface_ptr = NULL
HRESULT hr = CoCreateInstance(CLSID_SomeClass, NULL, CLSCTX_ALL,
IID_ISomeInterface, (void**)&interface_ptr);
在這個例子中,使用COM子系統取得指向ISomeInterface
介面的實現物件的指標,用CLSID_SomeClass指示用這個特定的coclass。
所有COM物件採用參照計數管理物件的生命期。客戶程式通過所有COM物件都要強制實現的IUnknown介面的AddRef與Release方法來控制參照計數。當參照計數降到0時,COM物件自己負責釋放主記憶體。即對動態分配主記憶體建立的COM物件,其Release函式內部參照計數降為0時,就釋放自身所占的動態分配主記憶體。有的COM物件(如IClassFactory)往往是靜態物件,Release函式內部參照計數降為0時不需做額外的操作。
特定語言(例如Visual Basic)提供了自動參照計數,所以COM物件開發者在原始碼中不需要顯式維護任何內部的參照計數。C/C++編程者或者執行顯式的參照計數,或者使用智慧型指標(如MFC提供的CComPtr)自動管理參照計數[需要解釋]。
下述是如何呼叫COM物件的AddRef與Release的指引:
不向遠端物件發出參照計數的呼叫。代理模組保持著遠端物件的一個參照,並維持著它自己的本機參照計數。
為簡化COM開發,引入了活動範本媒體櫃(Active Template Library,ATL)用於C++開發。ATL提供了更高層次的COM開發範式。ATL也有益於COM客戶應用程式開發擺脫直接維護參照計數,而是用智慧型指標物件。
其他能直接支援COM的媒體櫃與語言還包括MFC Visual C++編譯器的COM支援[6]、VBScript、Visual Basic、ECMAScript(JavaScript)和Borland Delphi等。
COM是一個語言獨立的二進制標準,任何能夠理解與實現COM的二進制定義的資料類型與介面的語言都可以開發COM元件。
COM實現負責進入、離開COM環境,實例化與參照計數COM物件,查詢物件支援的介面,以及錯誤處理。
Microsoft Visual C++編譯器支援對C++語言的擴充:稱作C++ Attributes。[7]這些擴充被設計用於簡化COM開發,去除實現COM伺服器時大量臃腫的程式碼。[8]
在Windows作業系統中,COM類、介面、類型媒體櫃都會根據其GUID登記到Windows登錄檔。HKEY_CLASSES_ROOT\CLSID下是COM類;HKEY_CLASSES_ROOT\Interface下是介面。COM類型媒體櫃註冊在每個COM物件的本機媒體櫃條目下或者遠端服務的網路位置處。
不使用登錄檔的COM(RegFree COM)是Windows XP引入的技術,允許COM元件不在登錄檔中存期啟用的元資料與類ID(CLSID),而是在實現類的assembly manifest或者儲存在可執行檔的資源中或元件安裝時的單獨檔案中。[9]這使得同一元件的不同版本可以安裝在不同目錄下,用其各自的manifest描述,直接複製安裝。[10]這種技術有限支援EXE COM伺服器[11]且不能用於系統範圍元件如MDAC、MSXML、DirectX或Internet Explorer。
應用程式裝入時,Windows裝入器搜尋manifest。[12]如果存在,裝入器從它增加資訊到啟用上下文。[10]COM類工廠試圖實例化一個類時,啟用上下文首先檢查這個CLSID的實現是否可以找到。僅當尋找失敗時,才掃描Windows登錄檔。[10]
COM物件可以透明地實例化與參照在同一處理程序、跨處理程序邊界、甚至在網上遠端(DCOM)。處理程序外或遠端物件用marshalling序列化方法呼叫與返回值。這種marshalling對使用者是不可見的,就如同訪問處理程序內的COM物件。
一個處理程序載入了一個COM的DLL檔案後,該DLL可能定義並使用了一些可修改的全域變數或訪問共享資源。該處理程序內的多個執行緒如何並行訪問該DLL並保證是執行緒安全的,這就是「套間」(apartment)技術需要解決的問題。
COM物件與建立或呼叫COM物件的執行緒可以按兩種策略來實現並行安全:
COM的並行安全的具體實現,提出了套間(apartment)概念。每一種套間類型表示在一個處理程序內部是多執行緒情況下,如何同步對COM物件的呼叫。套間是一個邏輯容器,收納遵循相同執行緒訪問規則的COM物件與COM執行緒(建立了COM物件的執行緒或者呼叫了COM物件的方法的執行緒)。套間本質上只是一個邏輯概念而非物理實體,沒有控制代碼類型可以參照它,更沒有可呼叫的API操縱它。套間有兩種:
一個COM物件只能存在於一個套間。COM物件一經建立就確定所屬套間,並且直到銷毀它一直存在於這個套間。COM物件的套間類型寫在Windows登錄檔相關條目中。
一個COM執行緒從建立到結束都屬於同一個套間。COM執行緒只有兩種套間模式:STA或MTA。[14]執行緒必須通過呼叫CoInitializeEx()函式並且設定參數為COINIT_APARTMENTTHREADED或者COINIT_MULTITHREADED,來指明該執行緒的套間模式。呼叫了CoInitializeEx()函式的執行緒即已進入套間,直到執行緒呼叫CoUninitialize()函式或者自身終止,才會離開套間。COM為每個STA的執行緒自動建立了一個隱藏視窗,其Windows class是"OleMainThreadWndClass" 。跨套間呼叫這個STA套間內的COM物件,實際上是向這個隱藏視窗傳送了一條視窗訊息,通過訊息迴圈與分派,該視窗過程收到這條視窗訊息並呼叫相應的COM物件的介面方法。
執行緒訪問屬於同一套間的COM物件,直接執行方法呼叫而不需COM設施的輔助。執行緒跨套間邊界去呼叫COM物件,傳遞的指標需要marshalling。如果通過標準的COM的API來呼叫,可以自動完成安整。例如,把一個COM介面指標作為參數傳遞給另外一個套間的COM物件的proxy的情形。但如果軟體編程者跨套間傳遞介面指標而沒有使用標準COM機制,就需要手工完成安整(通過CoMarshalInterThreadInterfaceInStream函式)與反安整(通過CoGetInterfaceAndReleaseStream函式取得COM介面的proxy)。例如,把COM介面指標作為執行緒啟動時的參數傳遞的情形。
跨處理程序的呼叫COM物件類似於同一處理程序內跨套間的呼叫COM物件。
COM物件coclass在登錄檔表示中的子鍵InProcServer32下的條目中ThreadingModel給出:
ThreadingModel的值 | 描述 |
---|---|
Legacy STA(ThreadingModel=Single或空 ) | 該COM物件屬於處理程序的第一個STA執行緒,通常是UI介面的執行緒。這是在過去單核CPU時代沒有遺留下來的。 |
單執行緒套間[15](STA),(ThreadingModel=Apartment) | 一個單獨的執行緒專門用於執行COM物件的方法。如果是STA的COM執行緒建立了STA的COM物件,這個COM物件的方法就由該執行緒執行,該執行緒呼叫該COM物件是直接呼叫。如果MTA的COM執行緒建立了STA的COM物件,系統在當前處理程序內自動建立一個default STA執行緒來執行該STA的COM物件的方法,並把COM物件的proxy返回該MTA的執行緒。COM物件所在STA套間之外的執行緒呼叫該COM物件的方法,需要對COM物件的指標先做marshalling再由作業系統自動排隊(通過該COM物件被呼叫方法所在的執行緒的標準的Microsoft Windows的訊息迴圈)。這提供了自動同步以確保物件的方法每次呼叫執行完畢後才能啟動方法的新的呼叫。開發者不需要擔心執行緒加鎖(locking)或競態條件。如果跨套間呼叫STA的COM物件,該物件所在STA的執行緒必須提供執行緒訊息迴圈處理機制。 |
多執行緒套間[16](MTA),(ThreadingModel=Free) | COM執行時不提供同步,多個MTA執行緒可以同時呼叫同一個MTA的COM物件,由各個MTA執行緒直接執行COM物件的方法,且因為在同一個MTA中因此不需要安整。COM物件需要自己實現同步控制以避免多執行緒同時訪問造成的競態條件或死結。STA的執行緒建立MTA的COM物件,系統自動建立一個或多個執行緒來執行MTA的COM物件。STA執行緒呼叫MTA的COM物件也需要marshalling,系統自動分配某個自動建立的執行緒來執行COM物件。MTA的優點是提高了並行處理效能,同時工作執行緒不需要有自己的Windows訊息迴圈。 |
自動選擇套間[17],(ThreadingModel=Both) | COM物件的套間類別與建立它的執行緒的套間類別一致。這避免了很多marshalling開銷,例如一個MTA伺服器被一個STA執行緒呼叫。 |
Thread Neutral Apartment(NA),(ThreadingModel=Neutral) | 一個特殊的套間,沒有任何指定的執行緒。當STA或MTA執行緒呼叫同一處理程序的NA物件,則呼叫執行緒臨時離開它的套間並執行COM物件的程式碼,沒有任何執行緒切換。即任何執行緒都可以直接了當呼叫COM物件的方法。[13]因此NA可以認為是最佳化套間之間方法呼叫的效率。 |
STA初始化時,建立一個隱藏視窗,用於apartment之間、行程間的訊息路由。該視窗必須有正常的訊息佇列泵。這種結構稱為訊息泵。早期版本的Windows,訊息泵的失敗會導致系統範圍的死結。這個問題被初始化COM的Windows API複雜化了,並會導致實現細節的洩露。
如果多個物件是循環參照(Circular reference),則可能會導致問題。
Objects may also be left with active reference counts if the 使用COM事件池(event sink)模型,則物件可能一直保持活動的參照計數而不能被銷毀。因為傳送事件的物件必須有處理事件的物件的參照,因而物件參照計數永遠不為0.
參照迴圈可以採取下述技術來克服:
處理程序內的COM元件是用DLL檔案實現,每個版本的DLL用CLSID登記到Windows登錄檔,因而某些情況下會發生DLL Hell效應。無需註冊的COM克服了這一問題。
COM元件間的約定,純粹是通過使用者與元件之間的語意保證和假設的形式來表示的。COM用類型的形式表示元件約定。但是該約定存在如下兩個關鍵問題,使得其對語意的表示並不是最佳的。
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.