4.1 COM接口類型概述
描述:除了Vtable結構的接口外,COM還支持另外兩種接口類型:即派發接口(Dispinterface)和雙向接口(Dual Interface)
4.1.1 Vtable 接口
1)Vtable類型接口的缺點是需要在編譯時與客戶機程序進行某種形式的綁定,也就是客戶機必須清楚編譯階段的接口方法和接口參數.由於這些靜態信息並不包含組件中方法的具體實現,所以COM的多態性剛好解決了這一問題.
2)一個接口的函數特徵是在編譯階段被定義的,而不是在接口方法的實際實現時被定義的
4.1.2 IDispatch 接口:Dispinterface
1)與Vtable不同,它可以在運行時決定方法的名字和參數,但速度慢.
4.1.3 雙向接口
1)一個雙向接口(dual-interface)就是一個普通的COM Vtable接口和一個Dispinerface接口的結合
4.2 組件及其接口的描述
描述:爲了使客戶訪問組件的接口,必須對接口進行描述
1)描述Vtable接口的佈局,每一個方法參數的類型以及參數的數量
2)對接口的描述可用於產生基於特定語言的綁定信息
3)提供支持調度功能,使客戶能夠跨進程和跨計算機訪問組件
4.2.1 類型信息
1)組件使用IDL對它們的接口進行描述.用MIDL.EXE可將它們編譯到類型庫中.
2)組件使用一個二進制的,獨立於語言的文件對它本身的功能進行描述,也就是type library(類型庫)
4.2.1.1 語言綁定
IDL所描述的組件,也可以產生特定語言的文件.MIDL編譯器只爲C/C++語言產生綁定信息,其他語言則使用類型庫來產生編譯時綁定.
4.2.1.2 生成PROXY/STUB
MIDL編譯IDL文件可得到proxy/stub DLL代碼以及make文件.proxy/stub DLL爲每一個接口實現標準調度
4.2.2 調度
1)概念:調度是指在進程和計算機之間進行函數參數和返回值傳輸的一個過程.
2)在多線程應用程序中,COM使用調度來同步對組件的訪問,因而即使你的組件只支持在進程內執行,有時也需要提供調度支持.
3)COM在實現調度時使用一個proxy和一個stub.調度時,客戶進程空間創建proxy對象,組件進程空間創建一個stub對象.
4)下列情況需要調度
a.訪問一臺計算機不同進程中的組件時
b.訪問不同計算機上的組件時(DCOM).
c.同一個進程組件的不同部分之間傳輸接口指針時.
4.3 分佈式COM
1)概念:分佈式COM是指COM可以將它的組件分佈在網絡中,並能夠在不同的計算機上對它進行訪問
4.4 標準調度
1)MIDL編譯器編譯IDL將產生一些文件用於構造proxy/stub DLL.
2)爲了能夠創建並註冊proxy/stub DLL,必須爲你的組件提供標準調度
3)proxy/stub必須在每一臺客戶機上註冊
MIDL 編譯產生的proxy/stub文件
文件 說明
ProjectNamePS.mk 該make文件生成名爲ProjectNamePS.dll的proxy/stub動態鏈接庫
ProjectName.h 與C 和 C++ 兼容的接口聲明頭文件
ProjectName_p.c 包含實現proxy/stub代碼的源文件
ProjectName_i.c 包含接口GUID的C源文件
DLLDATA.C 爲proxy/stub代碼實現DLL的C源文件
4.4.1 類型庫(通用)調度
1)類型庫使用標準的自動化調度器實現調度,前提是你的接口方法中只能使用與自動化(Automation)兼容的類型
2)爲了使用類型庫調度,應確保組件中只使用自動化類型,並在IDL文件的接口聲明中添加oleautomation屬性
// 由於IMath接口只使用與自動化兼容的類型,因此你可以使用類型庫調度
[
object,
uuid(8C30BC10-88F2-11D0-A756-B04A12000000),
oleautomation,
helpstring("IMath Interface"),
pointer_default(unique)
]
interface IMath : IUnknown
{
HRESULT Add([in]long,[in]long,[out,retval]long *);
HRESULT Subtract([in] long, [in] long, [out,retval]long *);
HRESULT Multiply([in] long, [in] long, [out,retval]long *);
HRESULT Divide([in] long, [in] long, [out,retval] long *);
};
3)實現類型庫調度所需的另一個步驟是,使用COM提供的RegisterTypeLibrary函數註冊組件的類型庫
4)ATL可以自動註冊你的類型庫
4.4.2 自定義調度
1)自定義調度可以使用任何機制實現進程間的通信,如RPC,TCP/IP..等等
2)自定義調度需要在你的組件中實現IMarshal接口
4.5 創建 Proxy/Stub DLL
1)定位 ProjectNamePS.mk 文件,運行命令創建DLL(ProjectNamePS.dll)
C:/MyProject>NMAKE -f ProjectNamePS.mk
2)將其加到註冊表中
C:/MyProject>REGSVR32 ProjectNamePS.dll
3)HKCR/Interfaces 註冊表鍵中列出了系統中所有接口所支持的proxy/stub DLL
4.6 接口定義語言
1)IDL 用於對接口以及一些詳細的信息進行描述,由它生成的結果文件被另外一種語言處理
4.6.1 基本語法和佈局
1)屬性值包含在一對方括號中,用於對關鍵字(如interface)進行修改
[
uuid(8C30BC10-88F2-11D0-A756-B04A12000000),
helpstring("IMath Interface"),
pointer_default(unique)
]
interface IMath:IUnknown {...};
2)如果IDL文件包含 library 關鍵字,MIDL 編譯器將產生一個類型庫.
3)每一個IDL關鍵字被它前面的屬性所修改
[
uuid(8C30BC10-88F2-11D0-A756-B04A12000000),
version(1.0),
helpstring("Chapter4_Server 1.0 Type Library")
]
library CHAPTER4_SERVERLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(5FB0C22F-3343-11D1-883A-444553540000),
helpstring("Math Class")
]
coclass Math
{
[default] interface IMath;
interface IMath2;
interface IAdvancedMath;
interface IComponentInfo;
};
};
4.6.2 接口的聲明:方法和屬性
1)如果想指定特殊的接口屬性,必須在interface關鍵字之前聲明,然後聲明該接口所支持的方法
[
object,
uuid(8C30BC10-88F2-11D0-A756-B04A12000000),
oleautomation,
helpstring("IMath Interface"),
pointer_default(unique)
]
interface IMath : IUnknown
{
[helpstring("Method Add")]
HRESULT Add([in]long,[in]long,[out,retval]long *);
[helpstring("Method Subtract")]
HRESULT Subtract([in] long, [in] long, [out,retval]long *);
HRESULT Multiply([in] long, [in] long, [out,retval]long *);
HRESULT Divide([in] long, [in] long, [out,retval] long *);
};
2)屬性代表組件的數據成員,可以對它直接賦值
interface IMath2:IUnknown
{
...
[propget,helpstring("property VesionNumber")]
HRESULT VersionNumber([out,retval]long * pVal);
[propput,helpstring("property VersionNumber")]
HRESULT VersionNumber([in] long newVal);
};
4.7 IDL 數據類型
1)使用內部的自動化調度器(automation marshaler)將接口參數的類型限制在自動化類型之內
4.7.1 數組
1)組件預先能夠知道樹組的大小
HRESULT Sum([in] short sArray[5],[out,retval]long *plResult);
2)通過size_is屬性向服務器傳送任意大小的數組
HRESULT Sum([in] short sArraySize,
[in,size_is(sArraySize)]short sArray[],
[out,retval]long *plResult);
而服務器上代碼非常簡單
STDMETHODIMP CMath::Sum(short sArraySize,short sArray[],long *plResult)
{
*plResult = 0;
while(sArraySize)
{
*plResult += sArray[--sArraySize];
}
return S_OK;
}
4.7.2 字符串
1) 用string屬性來描述
typedef struct tagCOMPONENT_INFO
{
[string]char* pstrAuthor;
...
} COMPONENT_INFO;
4.7.3 結構
1)由於IDL可以爲組件的方法自動產生proxy/stub代碼,因此我們可以構造複雜的數據結構在COM接口進行參數傳遞
typedef struct COMPONENT_INFO
{
[string]char * pstrAuthor;
short sMajor;
short sMinor;
BSTR bstrName;
}COMPONENT_INFO;
interface IComponentInfo:IUnknown
{
[helpstring("method get_Info")]
HRESULT get_Info([out]COMPONENT_INFO **pInfo);
[helpstring("method get_Name")]
HRESULT get_Name([out] BSTR * bstrName);
};
4.7.4 ENUM 類型
1)IDL支持枚舉類型,但它是與語言無關的
typedef
[
uuid(8C30BC10-88F2-11D0-A756-B04A12000000),
helpstring("Math Operation Type")
]
enum mathOPERATION
{
[helpstring("Add")] mathAdd = 0x0001,
[helpstring("Subtract")] mathSubtract = 0x0002,
[helpstring("Multiply")] mathMultiply = 0x0003,
[helpstring("Divide")] mathDivide = 0x0004
} mathOPERATION;
//定義後即可使用
[helpstring("method Compute")]
HRESULT Compute([in] mathOPERATION enumOp,
[in] long lOp1,
[in] long lOp2,
[out,retval]long * plResult );
使用MIDL編譯上面代碼,將產生一個C construct,並把它作爲Chapter4_Server頭文件的一部分
2)由MIDL爲組件生成的類型苦也包含對結構類型的定義,非C++也可以使用該枚舉類型
4.8 ATL 及 COM 數據類型
4.8.1 接口指針
1)接口指針實際是指向一個C++抽象類Vtable的指針
2)有時候返回接口指針是很有必要的
// IDL entry
[propget,helpstring("property AdvancedMath")]
HRESULT AdvancedMath([out,retval]IAdvancedMath **ppVal);
// Implementation
STDMETHODIMP CMath::get_AdvancedMath(IAdvancedMath **ppVal)
{
GetUnknown()->QueryInterface(IID_IAdvancedMath,(void **)ppVal);
return S_OK;
}
4.8.2 C++ 智能指針
1)智能指針(Smart pointer)指的是在處理指針時隱藏了許多內存管理技術的C++類
2)智能指針封裝了QueryInterface()/Release() 和 CoCreateInstance()/Release()兩對方法,使用戶不必擔心COM指針是否被釋放.
3)ATL 提供了兩個智能指針類:CComPtr 和 CComQIPtr
4.8.3 CComPtr
1)釋放CComPtr指針可以明確調用ptrMath.Release 或 ptrMath = 0,或不做處理
2)使用CComPtr
CComPtr<IMath> ptrMath;
HRESULT hr;
// This time use CoCreateInstance
hr = CoCreateInstance(CLSID_Math,
NULL,
CLSCTX_LOCAL_SERVER,
IID_IMath,
(void **)&ptrMath);
// Access the IMath interface
long lResult;
ptrMath->Add(134,353,&lResult);
cout<<"134 + 353 =" << lResult << endl;
...
3)注意點:由於CComPtr直接指向對外公開的一個Release方法,因此有可能錯誤地兩次釋放接口資源.多次調用Release方法可能會導致資源非法訪問
4.8.4 CComQIPtr
1)CComQIPtr 當被實例化時將自動調用QueryInterface
2)CComQIPtr需要請求接口的IID作爲參數
// Access IAdvancedMath
CComQIPtr <IAdvancedMath,&IID_IAdvancedMath> ptrAdvancedMath(ptrMath);
if (ptrAdvancedMath)
{
ptrAdvancedMath->Factorial(12,&lResult);
cout << "12! = " << lResult << endl;
ptrAdvancedMath->Fibonacci(12,&lResult);
cout << "The Fibonacci of 12 = " << lResult << endl;
}
3)Visual C++ 爲 COM 接口提供了一個智能指針的內部實現._com_ptr_ 類提供了該實現.
4.8.5 BSTR
1)BSTR被聲明爲OLECHAR *,它是一個Unicode字符串.Win32函數可以方便將Unicode和ANSI字符串轉換成BSTR類型
BSTR bstrDescription = 0;
BSTR bstrSource = 0;
pEl->GetDescription(&bstrDecription);
pEl->GetSource(&bstrSource);
USES_CONVERSION;
cout << OLE2T(bstrDecription) << endl;
cout << OLE2T(bstrSource) << endl;
::SysFreeString(bstrDescription);
::SysFreeString(bstrSource);
4.8.6 CComBSTR
1)CComBSTR是COM的BSTR類型的一個簡單封裝類
CComBSTR 類方法
成員函數 說明
CComBSTR(...) 針對ANSI,Unicode以及BSTR字符串類型,CComBSTR類提供了多個構造函數
Append 將一個ANSI字符串附加到一個BSTR字符串的後面
AppendBSTR 附加一個BSTR字符串
Copy 返回一個BSTR的複製
Length 返回字符串的長度(字符的個數)
2)比較CComBSTR字符串
// BSTR comparison
CComBSTR bstrA("COM");
CComBSTR bstrB("COM");
if (::SysStringByteLen(bstrA) == ::SysStringByteLen(bstrB) && ::memcmp(bstrA,bstrB,::SysStingByteLen(bstrA)) == 0)
{
cout << "bstrA == bstrB" << endl;
}
4.9 COM 的內存管理
1)在許多情況下,族件必須分配系統內存,並把它返回給客戶機程序,但有時組件並不知道客戶機程序什麼時候停止使用所分配的內存.COM提供了幾個專門的API,來處理這種問題
4.9.1 CoTaskMemAlloc 和 CoTaskMemFree
1)它們提供了內存函數(malloc和free)的一個封裝.
COMPONENT_INFO *pInfo = (COMPONENT_INFO *)CoTaskMemAlloc(sizeof(COMPONENT_INFO));
ZeroMemory(pInfo,sizeof(COMPONENT_INFO));
// Do something with the structure
...
// Now free it
CoTaskMemFree(pInfo);
4.9.2 IDL 和內存管理
1)爲了弄清楚又誰讀一內存的分配和釋放負責,必須查看IDL中方法的聲明.通過 in 和 out 參數屬性來指定.
2)規則如下:
a.對於只帶 in 屬性的參數,客戶機程序負責分配和釋放這些參數所需要的內存
b.對於只帶 out 屬性的參數,服務器負責分配這些參數所需要的內存,而客戶機負責釋放參數所佔用的內存
c.對於具備in/out屬性的參數,客戶機分配內存並負責釋放參數所佔用的內存.
3)示例:
// Look up the ProgID
WCHAR * pProgID = 0;
ProgIDFromCLSID(guids[0],&pProgID);
// Add it to the listbox
USES_CONVERSION;
m_ControlList.AddString(W2A(pProgID));
// Free the memory
CoTaskMemFree(pProgID);
4.10 COM 中的錯誤處理
1)所有的COM接口方法的返回類型要麼是void型,要麼是HRESULT型.組件向客戶機報告錯誤的基本機制一般是通過HRESULT返回碼
4.10.1 ISupportErrorInfo
4.10.2 CreateErrorInfo 和 ICreateErrorInfo
1)當一個組件需要返回錯誤信息時,它可以通過CreateErrorInfo函數來創建錯誤信息.
2)在組件內創建並初始化一個錯誤對象
ICreateErrorInfo *plCEl;
if (SUCCEEDED(CreateErrorInfo(&plCEl)))
{
// Set the GUID
plCEl->SetGUID(iid);
// Set the ProgID
LPOLESTR lpsz;
ProgIDFromCLSID(clsid,&lpsz);
if (lpsz != NULL)
{
plCEl->SetSource(lpsz);
CoTaskMemFree(lpsz);
}
// Set any help infomation
if (dwHelpID != 0 && lpszHelpFile != NULL)
{
plCEl->SetHelpContext(dwHelpID);
plCEl->SetHelpFile(const_cast<LPOLESTR>(lpszHelpFile));
}
// Set the actual description of the problem
plCEl->SetDescription((LPOLESTR)lpszDesc);
// Associate the error with the current execution context
IErrorInfo *pErrorInfo;
if (SUCCEEDED(plCEl->QueryInterface(IID_IErrorInfo,(void **)&pErrorInfo)))
SetErrorInfo(0,pErrorInfo);
// Release the interface
plCEl->Release();
pErrorInfo->Release();
}
4.10.3 SetErrorInfo 和 IErrorInfo
1)一個錯誤對象可以使用CreateErrorInfo 和ICreateErrorInfo 接口來創建
2)創建完錯誤對象後可以用SetErrorInfo 和 IErrorInfo 將它們與當前正在執行的線程相關聯。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/wallimn/archive/2006/04/17/667022.aspx